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, Value};
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 with schema support enabled.
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: Some(SchemaCatalog::new()),
49        }
50    }
51
52    /// Creates a new catalog with schema constraints enabled.
53    ///
54    /// This is now equivalent to `new()` since schema is always enabled.
55    #[must_use]
56    pub fn with_schema() -> Self {
57        Self::new()
58    }
59
60    // === Label Operations ===
61
62    /// Gets or creates a label ID for the given label name.
63    pub fn get_or_create_label(&self, name: &str) -> LabelId {
64        self.labels.get_or_create(name)
65    }
66
67    /// Gets the label ID for a label name, if it exists.
68    #[must_use]
69    pub fn get_label_id(&self, name: &str) -> Option<LabelId> {
70        self.labels.get_id(name)
71    }
72
73    /// Gets the label name for a label ID, if it exists.
74    #[must_use]
75    pub fn get_label_name(&self, id: LabelId) -> Option<Arc<str>> {
76        self.labels.get_name(id)
77    }
78
79    /// Returns the number of distinct labels.
80    #[must_use]
81    pub fn label_count(&self) -> usize {
82        self.labels.count()
83    }
84
85    /// Returns all label names.
86    #[must_use]
87    pub fn all_labels(&self) -> Vec<Arc<str>> {
88        self.labels.all_names()
89    }
90
91    // === Property Key Operations ===
92
93    /// Gets or creates a property key ID for the given property key name.
94    pub fn get_or_create_property_key(&self, name: &str) -> PropertyKeyId {
95        self.property_keys.get_or_create(name)
96    }
97
98    /// Gets the property key ID for a property key name, if it exists.
99    #[must_use]
100    pub fn get_property_key_id(&self, name: &str) -> Option<PropertyKeyId> {
101        self.property_keys.get_id(name)
102    }
103
104    /// Gets the property key name for a property key ID, if it exists.
105    #[must_use]
106    pub fn get_property_key_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
107        self.property_keys.get_name(id)
108    }
109
110    /// Returns the number of distinct property keys.
111    #[must_use]
112    pub fn property_key_count(&self) -> usize {
113        self.property_keys.count()
114    }
115
116    /// Returns all property key names.
117    #[must_use]
118    pub fn all_property_keys(&self) -> Vec<Arc<str>> {
119        self.property_keys.all_names()
120    }
121
122    // === Edge Type Operations ===
123
124    /// Gets or creates an edge type ID for the given edge type name.
125    pub fn get_or_create_edge_type(&self, name: &str) -> EdgeTypeId {
126        self.edge_types.get_or_create(name)
127    }
128
129    /// Gets the edge type ID for an edge type name, if it exists.
130    #[must_use]
131    pub fn get_edge_type_id(&self, name: &str) -> Option<EdgeTypeId> {
132        self.edge_types.get_id(name)
133    }
134
135    /// Gets the edge type name for an edge type ID, if it exists.
136    #[must_use]
137    pub fn get_edge_type_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
138        self.edge_types.get_name(id)
139    }
140
141    /// Returns the number of distinct edge types.
142    #[must_use]
143    pub fn edge_type_count(&self) -> usize {
144        self.edge_types.count()
145    }
146
147    /// Returns all edge type names.
148    #[must_use]
149    pub fn all_edge_types(&self) -> Vec<Arc<str>> {
150        self.edge_types.all_names()
151    }
152
153    // === Index Operations ===
154
155    /// Creates a new index on a label and property key.
156    pub fn create_index(
157        &self,
158        label: LabelId,
159        property_key: PropertyKeyId,
160        index_type: IndexType,
161    ) -> IndexId {
162        self.indexes.create(label, property_key, index_type)
163    }
164
165    /// Drops an index by ID.
166    pub fn drop_index(&self, id: IndexId) -> bool {
167        self.indexes.drop(id)
168    }
169
170    /// Gets the index definition for an index ID.
171    #[must_use]
172    pub fn get_index(&self, id: IndexId) -> Option<IndexDefinition> {
173        self.indexes.get(id)
174    }
175
176    /// Finds indexes for a given label.
177    #[must_use]
178    pub fn indexes_for_label(&self, label: LabelId) -> Vec<IndexId> {
179        self.indexes.for_label(label)
180    }
181
182    /// Finds indexes for a given label and property key.
183    #[must_use]
184    pub fn indexes_for_label_property(
185        &self,
186        label: LabelId,
187        property_key: PropertyKeyId,
188    ) -> Vec<IndexId> {
189        self.indexes.for_label_property(label, property_key)
190    }
191
192    /// Returns the number of indexes.
193    #[must_use]
194    pub fn index_count(&self) -> usize {
195        self.indexes.count()
196    }
197
198    // === Schema Operations ===
199
200    /// Returns whether schema constraints are enabled.
201    #[must_use]
202    pub fn has_schema(&self) -> bool {
203        self.schema.is_some()
204    }
205
206    /// Adds a uniqueness constraint.
207    ///
208    /// Returns an error if schema is not enabled or constraint already exists.
209    pub fn add_unique_constraint(
210        &self,
211        label: LabelId,
212        property_key: PropertyKeyId,
213    ) -> Result<(), CatalogError> {
214        match &self.schema {
215            Some(schema) => schema.add_unique_constraint(label, property_key),
216            None => Err(CatalogError::SchemaNotEnabled),
217        }
218    }
219
220    /// Adds a required property constraint (NOT NULL).
221    ///
222    /// Returns an error if schema is not enabled or constraint already exists.
223    pub fn add_required_property(
224        &self,
225        label: LabelId,
226        property_key: PropertyKeyId,
227    ) -> Result<(), CatalogError> {
228        match &self.schema {
229            Some(schema) => schema.add_required_property(label, property_key),
230            None => Err(CatalogError::SchemaNotEnabled),
231        }
232    }
233
234    /// Checks if a property is required for a label.
235    #[must_use]
236    pub fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
237        self.schema
238            .as_ref()
239            .is_some_and(|s| s.is_property_required(label, property_key))
240    }
241
242    /// Checks if a property must be unique for a label.
243    #[must_use]
244    pub fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
245        self.schema
246            .as_ref()
247            .is_some_and(|s| s.is_property_unique(label, property_key))
248    }
249
250    // === Type Definition Operations ===
251
252    /// Returns a reference to the schema catalog.
253    #[must_use]
254    pub fn schema(&self) -> Option<&SchemaCatalog> {
255        self.schema.as_ref()
256    }
257
258    /// Registers a node type definition.
259    pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
260        match &self.schema {
261            Some(schema) => schema.register_node_type(def),
262            None => Err(CatalogError::SchemaNotEnabled),
263        }
264    }
265
266    /// Registers or replaces a node type definition.
267    pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
268        if let Some(schema) = &self.schema {
269            schema.register_or_replace_node_type(def);
270        }
271    }
272
273    /// Drops a node type definition.
274    pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
275        match &self.schema {
276            Some(schema) => schema.drop_node_type(name),
277            None => Err(CatalogError::SchemaNotEnabled),
278        }
279    }
280
281    /// Gets a node type definition by name.
282    #[must_use]
283    pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
284        self.schema.as_ref().and_then(|s| s.get_node_type(name))
285    }
286
287    /// Returns all registered node type names.
288    #[must_use]
289    pub fn all_node_type_names(&self) -> Vec<String> {
290        self.schema
291            .as_ref()
292            .map(SchemaCatalog::all_node_types)
293            .unwrap_or_default()
294    }
295
296    /// Registers an edge type definition.
297    pub fn register_edge_type_def(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
298        match &self.schema {
299            Some(schema) => schema.register_edge_type(def),
300            None => Err(CatalogError::SchemaNotEnabled),
301        }
302    }
303
304    /// Registers or replaces an edge type definition.
305    pub fn register_or_replace_edge_type_def(&self, def: EdgeTypeDefinition) {
306        if let Some(schema) = &self.schema {
307            schema.register_or_replace_edge_type(def);
308        }
309    }
310
311    /// Drops an edge type definition.
312    pub fn drop_edge_type_def(&self, name: &str) -> Result<(), CatalogError> {
313        match &self.schema {
314            Some(schema) => schema.drop_edge_type(name),
315            None => Err(CatalogError::SchemaNotEnabled),
316        }
317    }
318
319    /// Gets an edge type definition by name.
320    #[must_use]
321    pub fn get_edge_type_def(&self, name: &str) -> Option<EdgeTypeDefinition> {
322        self.schema.as_ref().and_then(|s| s.get_edge_type(name))
323    }
324
325    /// Registers a graph type definition.
326    pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
327        match &self.schema {
328            Some(schema) => schema.register_graph_type(def),
329            None => Err(CatalogError::SchemaNotEnabled),
330        }
331    }
332
333    /// Drops a graph type definition.
334    pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
335        match &self.schema {
336            Some(schema) => schema.drop_graph_type(name),
337            None => Err(CatalogError::SchemaNotEnabled),
338        }
339    }
340
341    /// Registers a schema namespace.
342    pub fn register_schema_namespace(&self, name: String) -> Result<(), CatalogError> {
343        match &self.schema {
344            Some(schema) => schema.register_schema(name),
345            None => Err(CatalogError::SchemaNotEnabled),
346        }
347    }
348
349    /// Drops a schema namespace.
350    pub fn drop_schema_namespace(&self, name: &str) -> Result<(), CatalogError> {
351        match &self.schema {
352            Some(schema) => schema.drop_schema(name),
353            None => Err(CatalogError::SchemaNotEnabled),
354        }
355    }
356
357    /// Adds a property to a node type.
358    pub fn alter_node_type_add_property(
359        &self,
360        type_name: &str,
361        property: TypedProperty,
362    ) -> Result<(), CatalogError> {
363        match &self.schema {
364            Some(schema) => schema.alter_node_type_add_property(type_name, property),
365            None => Err(CatalogError::SchemaNotEnabled),
366        }
367    }
368
369    /// Drops a property from a node type.
370    pub fn alter_node_type_drop_property(
371        &self,
372        type_name: &str,
373        property_name: &str,
374    ) -> Result<(), CatalogError> {
375        match &self.schema {
376            Some(schema) => schema.alter_node_type_drop_property(type_name, property_name),
377            None => Err(CatalogError::SchemaNotEnabled),
378        }
379    }
380
381    /// Adds a property to an edge type.
382    pub fn alter_edge_type_add_property(
383        &self,
384        type_name: &str,
385        property: TypedProperty,
386    ) -> Result<(), CatalogError> {
387        match &self.schema {
388            Some(schema) => schema.alter_edge_type_add_property(type_name, property),
389            None => Err(CatalogError::SchemaNotEnabled),
390        }
391    }
392
393    /// Drops a property from an edge type.
394    pub fn alter_edge_type_drop_property(
395        &self,
396        type_name: &str,
397        property_name: &str,
398    ) -> Result<(), CatalogError> {
399        match &self.schema {
400            Some(schema) => schema.alter_edge_type_drop_property(type_name, property_name),
401            None => Err(CatalogError::SchemaNotEnabled),
402        }
403    }
404
405    /// Adds a node type to a graph type.
406    pub fn alter_graph_type_add_node_type(
407        &self,
408        graph_type_name: &str,
409        node_type: String,
410    ) -> Result<(), CatalogError> {
411        match &self.schema {
412            Some(schema) => schema.alter_graph_type_add_node_type(graph_type_name, node_type),
413            None => Err(CatalogError::SchemaNotEnabled),
414        }
415    }
416
417    /// Drops a node type from a graph type.
418    pub fn alter_graph_type_drop_node_type(
419        &self,
420        graph_type_name: &str,
421        node_type: &str,
422    ) -> Result<(), CatalogError> {
423        match &self.schema {
424            Some(schema) => schema.alter_graph_type_drop_node_type(graph_type_name, node_type),
425            None => Err(CatalogError::SchemaNotEnabled),
426        }
427    }
428
429    /// Adds an edge type to a graph type.
430    pub fn alter_graph_type_add_edge_type(
431        &self,
432        graph_type_name: &str,
433        edge_type: String,
434    ) -> Result<(), CatalogError> {
435        match &self.schema {
436            Some(schema) => schema.alter_graph_type_add_edge_type(graph_type_name, edge_type),
437            None => Err(CatalogError::SchemaNotEnabled),
438        }
439    }
440
441    /// Drops an edge type from a graph type.
442    pub fn alter_graph_type_drop_edge_type(
443        &self,
444        graph_type_name: &str,
445        edge_type: &str,
446    ) -> Result<(), CatalogError> {
447        match &self.schema {
448            Some(schema) => schema.alter_graph_type_drop_edge_type(graph_type_name, edge_type),
449            None => Err(CatalogError::SchemaNotEnabled),
450        }
451    }
452
453    /// Binds a graph instance to a graph type.
454    pub fn bind_graph_type(
455        &self,
456        graph_name: &str,
457        graph_type: String,
458    ) -> Result<(), CatalogError> {
459        match &self.schema {
460            Some(schema) => {
461                // Verify the graph type exists
462                if schema.get_graph_type(&graph_type).is_none() {
463                    return Err(CatalogError::TypeNotFound(graph_type));
464                }
465                schema
466                    .graph_type_bindings
467                    .write()
468                    .insert(graph_name.to_string(), graph_type);
469                Ok(())
470            }
471            None => Err(CatalogError::SchemaNotEnabled),
472        }
473    }
474
475    /// Gets the graph type binding for a graph instance.
476    pub fn get_graph_type_binding(&self, graph_name: &str) -> Option<String> {
477        self.schema
478            .as_ref()?
479            .graph_type_bindings
480            .read()
481            .get(graph_name)
482            .cloned()
483    }
484
485    /// Registers a stored procedure.
486    pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
487        match &self.schema {
488            Some(schema) => schema.register_procedure(def),
489            None => Err(CatalogError::SchemaNotEnabled),
490        }
491    }
492
493    /// Replaces or creates a stored procedure.
494    pub fn replace_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
495        match &self.schema {
496            Some(schema) => {
497                schema.replace_procedure(def);
498                Ok(())
499            }
500            None => Err(CatalogError::SchemaNotEnabled),
501        }
502    }
503
504    /// Drops a stored procedure.
505    pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
506        match &self.schema {
507            Some(schema) => schema.drop_procedure(name),
508            None => Err(CatalogError::SchemaNotEnabled),
509        }
510    }
511
512    /// Gets a stored procedure by name.
513    pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
514        self.schema.as_ref()?.get_procedure(name)
515    }
516}
517
518impl Default for Catalog {
519    fn default() -> Self {
520        Self::new()
521    }
522}
523
524// === Label Catalog ===
525
526/// Bidirectional mapping between label names and IDs.
527struct LabelCatalog {
528    name_to_id: RwLock<HashMap<Arc<str>, LabelId>>,
529    id_to_name: RwLock<Vec<Arc<str>>>,
530    next_id: AtomicU32,
531}
532
533impl LabelCatalog {
534    fn new() -> Self {
535        Self {
536            name_to_id: RwLock::new(HashMap::new()),
537            id_to_name: RwLock::new(Vec::new()),
538            next_id: AtomicU32::new(0),
539        }
540    }
541
542    fn get_or_create(&self, name: &str) -> LabelId {
543        // Fast path: check if already exists
544        {
545            let name_to_id = self.name_to_id.read();
546            if let Some(&id) = name_to_id.get(name) {
547                return id;
548            }
549        }
550
551        // Slow path: create new entry
552        let mut name_to_id = self.name_to_id.write();
553        let mut id_to_name = self.id_to_name.write();
554
555        // Double-check after acquiring write lock
556        if let Some(&id) = name_to_id.get(name) {
557            return id;
558        }
559
560        let id = LabelId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
561        let name: Arc<str> = name.into();
562        name_to_id.insert(Arc::clone(&name), id);
563        id_to_name.push(name);
564        id
565    }
566
567    fn get_id(&self, name: &str) -> Option<LabelId> {
568        self.name_to_id.read().get(name).copied()
569    }
570
571    fn get_name(&self, id: LabelId) -> Option<Arc<str>> {
572        self.id_to_name.read().get(id.as_u32() as usize).cloned()
573    }
574
575    fn count(&self) -> usize {
576        self.id_to_name.read().len()
577    }
578
579    fn all_names(&self) -> Vec<Arc<str>> {
580        self.id_to_name.read().clone()
581    }
582}
583
584// === Property Catalog ===
585
586/// Bidirectional mapping between property key names and IDs.
587struct PropertyCatalog {
588    name_to_id: RwLock<HashMap<Arc<str>, PropertyKeyId>>,
589    id_to_name: RwLock<Vec<Arc<str>>>,
590    next_id: AtomicU32,
591}
592
593impl PropertyCatalog {
594    fn new() -> Self {
595        Self {
596            name_to_id: RwLock::new(HashMap::new()),
597            id_to_name: RwLock::new(Vec::new()),
598            next_id: AtomicU32::new(0),
599        }
600    }
601
602    fn get_or_create(&self, name: &str) -> PropertyKeyId {
603        // Fast path: check if already exists
604        {
605            let name_to_id = self.name_to_id.read();
606            if let Some(&id) = name_to_id.get(name) {
607                return id;
608            }
609        }
610
611        // Slow path: create new entry
612        let mut name_to_id = self.name_to_id.write();
613        let mut id_to_name = self.id_to_name.write();
614
615        // Double-check after acquiring write lock
616        if let Some(&id) = name_to_id.get(name) {
617            return id;
618        }
619
620        let id = PropertyKeyId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
621        let name: Arc<str> = name.into();
622        name_to_id.insert(Arc::clone(&name), id);
623        id_to_name.push(name);
624        id
625    }
626
627    fn get_id(&self, name: &str) -> Option<PropertyKeyId> {
628        self.name_to_id.read().get(name).copied()
629    }
630
631    fn get_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
632        self.id_to_name.read().get(id.as_u32() as usize).cloned()
633    }
634
635    fn count(&self) -> usize {
636        self.id_to_name.read().len()
637    }
638
639    fn all_names(&self) -> Vec<Arc<str>> {
640        self.id_to_name.read().clone()
641    }
642}
643
644// === Edge Type Catalog ===
645
646/// Bidirectional mapping between edge type names and IDs.
647struct EdgeTypeCatalog {
648    name_to_id: RwLock<HashMap<Arc<str>, EdgeTypeId>>,
649    id_to_name: RwLock<Vec<Arc<str>>>,
650    next_id: AtomicU32,
651}
652
653impl EdgeTypeCatalog {
654    fn new() -> Self {
655        Self {
656            name_to_id: RwLock::new(HashMap::new()),
657            id_to_name: RwLock::new(Vec::new()),
658            next_id: AtomicU32::new(0),
659        }
660    }
661
662    fn get_or_create(&self, name: &str) -> EdgeTypeId {
663        // Fast path: check if already exists
664        {
665            let name_to_id = self.name_to_id.read();
666            if let Some(&id) = name_to_id.get(name) {
667                return id;
668            }
669        }
670
671        // Slow path: create new entry
672        let mut name_to_id = self.name_to_id.write();
673        let mut id_to_name = self.id_to_name.write();
674
675        // Double-check after acquiring write lock
676        if let Some(&id) = name_to_id.get(name) {
677            return id;
678        }
679
680        let id = EdgeTypeId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
681        let name: Arc<str> = name.into();
682        name_to_id.insert(Arc::clone(&name), id);
683        id_to_name.push(name);
684        id
685    }
686
687    fn get_id(&self, name: &str) -> Option<EdgeTypeId> {
688        self.name_to_id.read().get(name).copied()
689    }
690
691    fn get_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
692        self.id_to_name.read().get(id.as_u32() as usize).cloned()
693    }
694
695    fn count(&self) -> usize {
696        self.id_to_name.read().len()
697    }
698
699    fn all_names(&self) -> Vec<Arc<str>> {
700        self.id_to_name.read().clone()
701    }
702}
703
704// === Index Catalog ===
705
706/// Type of index.
707#[derive(Debug, Clone, Copy, PartialEq, Eq)]
708pub enum IndexType {
709    /// Hash index for equality lookups.
710    Hash,
711    /// BTree index for range queries.
712    BTree,
713    /// Full-text index for text search.
714    FullText,
715}
716
717/// Index definition.
718#[derive(Debug, Clone)]
719pub struct IndexDefinition {
720    /// The index ID.
721    pub id: IndexId,
722    /// The label this index applies to.
723    pub label: LabelId,
724    /// The property key being indexed.
725    pub property_key: PropertyKeyId,
726    /// The type of index.
727    pub index_type: IndexType,
728}
729
730/// Manages index definitions.
731struct IndexCatalog {
732    indexes: RwLock<HashMap<IndexId, IndexDefinition>>,
733    label_indexes: RwLock<HashMap<LabelId, Vec<IndexId>>>,
734    label_property_indexes: RwLock<HashMap<(LabelId, PropertyKeyId), Vec<IndexId>>>,
735    next_id: AtomicU32,
736}
737
738impl IndexCatalog {
739    fn new() -> Self {
740        Self {
741            indexes: RwLock::new(HashMap::new()),
742            label_indexes: RwLock::new(HashMap::new()),
743            label_property_indexes: RwLock::new(HashMap::new()),
744            next_id: AtomicU32::new(0),
745        }
746    }
747
748    fn create(
749        &self,
750        label: LabelId,
751        property_key: PropertyKeyId,
752        index_type: IndexType,
753    ) -> IndexId {
754        let id = IndexId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
755        let definition = IndexDefinition {
756            id,
757            label,
758            property_key,
759            index_type,
760        };
761
762        let mut indexes = self.indexes.write();
763        let mut label_indexes = self.label_indexes.write();
764        let mut label_property_indexes = self.label_property_indexes.write();
765
766        indexes.insert(id, definition);
767        label_indexes.entry(label).or_default().push(id);
768        label_property_indexes
769            .entry((label, property_key))
770            .or_default()
771            .push(id);
772
773        id
774    }
775
776    fn drop(&self, id: IndexId) -> bool {
777        let mut indexes = self.indexes.write();
778        let mut label_indexes = self.label_indexes.write();
779        let mut label_property_indexes = self.label_property_indexes.write();
780
781        if let Some(definition) = indexes.remove(&id) {
782            // Remove from label index
783            if let Some(ids) = label_indexes.get_mut(&definition.label) {
784                ids.retain(|&i| i != id);
785            }
786            // Remove from label-property index
787            if let Some(ids) =
788                label_property_indexes.get_mut(&(definition.label, definition.property_key))
789            {
790                ids.retain(|&i| i != id);
791            }
792            true
793        } else {
794            false
795        }
796    }
797
798    fn get(&self, id: IndexId) -> Option<IndexDefinition> {
799        self.indexes.read().get(&id).cloned()
800    }
801
802    fn for_label(&self, label: LabelId) -> Vec<IndexId> {
803        self.label_indexes
804            .read()
805            .get(&label)
806            .cloned()
807            .unwrap_or_default()
808    }
809
810    fn for_label_property(&self, label: LabelId, property_key: PropertyKeyId) -> Vec<IndexId> {
811        self.label_property_indexes
812            .read()
813            .get(&(label, property_key))
814            .cloned()
815            .unwrap_or_default()
816    }
817
818    fn count(&self) -> usize {
819        self.indexes.read().len()
820    }
821}
822
823// === Type Definitions ===
824
825/// Data type for a typed property in a node or edge type definition.
826#[derive(Debug, Clone, PartialEq, Eq)]
827pub enum PropertyDataType {
828    /// UTF-8 string.
829    String,
830    /// 64-bit signed integer.
831    Int64,
832    /// 64-bit floating point.
833    Float64,
834    /// Boolean.
835    Bool,
836    /// Calendar date.
837    Date,
838    /// Time of day.
839    Time,
840    /// Timestamp (date + time).
841    Timestamp,
842    /// Duration / interval.
843    Duration,
844    /// Ordered list of values.
845    List,
846    /// Key-value map.
847    Map,
848    /// Raw bytes.
849    Bytes,
850    /// Any type (no enforcement).
851    Any,
852}
853
854impl PropertyDataType {
855    /// Parses a type name string (case-insensitive) into a `PropertyDataType`.
856    #[must_use]
857    pub fn from_type_name(name: &str) -> Self {
858        match name.to_uppercase().as_str() {
859            "STRING" | "VARCHAR" | "TEXT" => Self::String,
860            "INT" | "INT64" | "INTEGER" | "BIGINT" => Self::Int64,
861            "FLOAT" | "FLOAT64" | "DOUBLE" | "REAL" => Self::Float64,
862            "BOOL" | "BOOLEAN" => Self::Bool,
863            "DATE" => Self::Date,
864            "TIME" => Self::Time,
865            "TIMESTAMP" | "DATETIME" => Self::Timestamp,
866            "DURATION" | "INTERVAL" => Self::Duration,
867            "LIST" | "ARRAY" => Self::List,
868            "MAP" | "RECORD" => Self::Map,
869            "BYTES" | "BINARY" | "BLOB" => Self::Bytes,
870            _ => Self::Any,
871        }
872    }
873
874    /// Checks whether a value conforms to this type.
875    #[must_use]
876    pub fn matches(&self, value: &Value) -> bool {
877        matches!(
878            (self, value),
879            (Self::Any, _)
880                | (_, Value::Null)
881                | (Self::String, Value::String(_))
882                | (Self::Int64, Value::Int64(_))
883                | (Self::Float64, Value::Float64(_))
884                | (Self::Bool, Value::Bool(_))
885                | (Self::Date, Value::Date(_))
886                | (Self::Time, Value::Time(_))
887                | (Self::Timestamp, Value::Timestamp(_))
888                | (Self::Duration, Value::Duration(_))
889                | (Self::List, Value::List(_))
890                | (Self::Bytes, Value::Bytes(_))
891        )
892    }
893}
894
895impl std::fmt::Display for PropertyDataType {
896    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
897        match self {
898            Self::String => write!(f, "STRING"),
899            Self::Int64 => write!(f, "INT64"),
900            Self::Float64 => write!(f, "FLOAT64"),
901            Self::Bool => write!(f, "BOOLEAN"),
902            Self::Date => write!(f, "DATE"),
903            Self::Time => write!(f, "TIME"),
904            Self::Timestamp => write!(f, "TIMESTAMP"),
905            Self::Duration => write!(f, "DURATION"),
906            Self::List => write!(f, "LIST"),
907            Self::Map => write!(f, "MAP"),
908            Self::Bytes => write!(f, "BYTES"),
909            Self::Any => write!(f, "ANY"),
910        }
911    }
912}
913
914/// A typed property within a node or edge type definition.
915#[derive(Debug, Clone)]
916pub struct TypedProperty {
917    /// Property name.
918    pub name: String,
919    /// Expected data type.
920    pub data_type: PropertyDataType,
921    /// Whether NULL values are allowed.
922    pub nullable: bool,
923    /// Default value (used when property is not explicitly set).
924    pub default_value: Option<Value>,
925}
926
927/// A constraint on a node or edge type.
928#[derive(Debug, Clone)]
929pub enum TypeConstraint {
930    /// Primary key (implies UNIQUE + NOT NULL).
931    PrimaryKey(Vec<String>),
932    /// Uniqueness constraint on one or more properties.
933    Unique(Vec<String>),
934    /// NOT NULL constraint on a single property.
935    NotNull(String),
936    /// CHECK constraint with a named expression string.
937    Check {
938        /// Optional constraint name.
939        name: Option<String>,
940        /// Expression (stored as string for now).
941        expression: String,
942    },
943}
944
945/// Definition of a node type (label schema).
946#[derive(Debug, Clone)]
947pub struct NodeTypeDefinition {
948    /// Type name (corresponds to a label).
949    pub name: String,
950    /// Typed property definitions.
951    pub properties: Vec<TypedProperty>,
952    /// Type-level constraints.
953    pub constraints: Vec<TypeConstraint>,
954}
955
956/// Definition of an edge type (relationship type schema).
957#[derive(Debug, Clone)]
958pub struct EdgeTypeDefinition {
959    /// Type name (corresponds to an edge type / relationship type).
960    pub name: String,
961    /// Typed property definitions.
962    pub properties: Vec<TypedProperty>,
963    /// Type-level constraints.
964    pub constraints: Vec<TypeConstraint>,
965}
966
967/// Definition of a graph type (constrains which node/edge types a graph allows).
968#[derive(Debug, Clone)]
969pub struct GraphTypeDefinition {
970    /// Graph type name.
971    pub name: String,
972    /// Allowed node types (empty = open).
973    pub allowed_node_types: Vec<String>,
974    /// Allowed edge types (empty = open).
975    pub allowed_edge_types: Vec<String>,
976    /// Whether unlisted types are permitted.
977    pub open: bool,
978}
979
980/// Definition of a stored procedure.
981#[derive(Debug, Clone)]
982pub struct ProcedureDefinition {
983    /// Procedure name.
984    pub name: String,
985    /// Parameter definitions: (name, type).
986    pub params: Vec<(String, String)>,
987    /// Return column definitions: (name, type).
988    pub returns: Vec<(String, String)>,
989    /// Raw GQL query body.
990    pub body: String,
991}
992
993// === Schema Catalog ===
994
995/// Schema constraints and type definitions.
996pub struct SchemaCatalog {
997    /// Properties that must be unique for a given label.
998    unique_constraints: RwLock<HashMap<(LabelId, PropertyKeyId), ()>>,
999    /// Properties that are required (NOT NULL) for a given label.
1000    required_properties: RwLock<HashMap<(LabelId, PropertyKeyId), ()>>,
1001    /// Registered node type definitions.
1002    node_types: RwLock<HashMap<String, NodeTypeDefinition>>,
1003    /// Registered edge type definitions.
1004    edge_types: RwLock<HashMap<String, EdgeTypeDefinition>>,
1005    /// Registered graph type definitions.
1006    graph_types: RwLock<HashMap<String, GraphTypeDefinition>>,
1007    /// Schema namespaces.
1008    schemas: RwLock<Vec<String>>,
1009    /// Graph instance to graph type bindings.
1010    graph_type_bindings: RwLock<HashMap<String, String>>,
1011    /// Stored procedure definitions.
1012    procedures: RwLock<HashMap<String, ProcedureDefinition>>,
1013}
1014
1015impl SchemaCatalog {
1016    fn new() -> Self {
1017        Self {
1018            unique_constraints: RwLock::new(HashMap::new()),
1019            required_properties: RwLock::new(HashMap::new()),
1020            node_types: RwLock::new(HashMap::new()),
1021            edge_types: RwLock::new(HashMap::new()),
1022            graph_types: RwLock::new(HashMap::new()),
1023            schemas: RwLock::new(Vec::new()),
1024            graph_type_bindings: RwLock::new(HashMap::new()),
1025            procedures: RwLock::new(HashMap::new()),
1026        }
1027    }
1028
1029    // --- Node type operations ---
1030
1031    /// Registers a new node type definition.
1032    pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
1033        let mut types = self.node_types.write();
1034        if types.contains_key(&def.name) {
1035            return Err(CatalogError::TypeAlreadyExists(def.name));
1036        }
1037        types.insert(def.name.clone(), def);
1038        Ok(())
1039    }
1040
1041    /// Registers or replaces a node type definition.
1042    pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
1043        self.node_types.write().insert(def.name.clone(), def);
1044    }
1045
1046    /// Drops a node type definition by name.
1047    pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
1048        let mut types = self.node_types.write();
1049        if types.remove(name).is_none() {
1050            return Err(CatalogError::TypeNotFound(name.to_string()));
1051        }
1052        Ok(())
1053    }
1054
1055    /// Gets a node type definition by name.
1056    #[must_use]
1057    pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1058        self.node_types.read().get(name).cloned()
1059    }
1060
1061    /// Returns all registered node type names.
1062    #[must_use]
1063    pub fn all_node_types(&self) -> Vec<String> {
1064        self.node_types.read().keys().cloned().collect()
1065    }
1066
1067    // --- Edge type operations ---
1068
1069    /// Registers a new edge type definition.
1070    pub fn register_edge_type(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
1071        let mut types = self.edge_types.write();
1072        if types.contains_key(&def.name) {
1073            return Err(CatalogError::TypeAlreadyExists(def.name));
1074        }
1075        types.insert(def.name.clone(), def);
1076        Ok(())
1077    }
1078
1079    /// Registers or replaces an edge type definition.
1080    pub fn register_or_replace_edge_type(&self, def: EdgeTypeDefinition) {
1081        self.edge_types.write().insert(def.name.clone(), def);
1082    }
1083
1084    /// Drops an edge type definition by name.
1085    pub fn drop_edge_type(&self, name: &str) -> Result<(), CatalogError> {
1086        let mut types = self.edge_types.write();
1087        if types.remove(name).is_none() {
1088            return Err(CatalogError::TypeNotFound(name.to_string()));
1089        }
1090        Ok(())
1091    }
1092
1093    /// Gets an edge type definition by name.
1094    #[must_use]
1095    pub fn get_edge_type(&self, name: &str) -> Option<EdgeTypeDefinition> {
1096        self.edge_types.read().get(name).cloned()
1097    }
1098
1099    /// Returns all registered edge type names.
1100    #[must_use]
1101    pub fn all_edge_types(&self) -> Vec<String> {
1102        self.edge_types.read().keys().cloned().collect()
1103    }
1104
1105    // --- Graph type operations ---
1106
1107    /// Registers a new graph type definition.
1108    pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
1109        let mut types = self.graph_types.write();
1110        if types.contains_key(&def.name) {
1111            return Err(CatalogError::TypeAlreadyExists(def.name));
1112        }
1113        types.insert(def.name.clone(), def);
1114        Ok(())
1115    }
1116
1117    /// Drops a graph type definition by name.
1118    pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
1119        let mut types = self.graph_types.write();
1120        if types.remove(name).is_none() {
1121            return Err(CatalogError::TypeNotFound(name.to_string()));
1122        }
1123        Ok(())
1124    }
1125
1126    /// Gets a graph type definition by name.
1127    #[must_use]
1128    pub fn get_graph_type(&self, name: &str) -> Option<GraphTypeDefinition> {
1129        self.graph_types.read().get(name).cloned()
1130    }
1131
1132    // --- Schema namespace operations ---
1133
1134    /// Registers a schema namespace.
1135    pub fn register_schema(&self, name: String) -> Result<(), CatalogError> {
1136        let mut schemas = self.schemas.write();
1137        if schemas.contains(&name) {
1138            return Err(CatalogError::SchemaAlreadyExists(name));
1139        }
1140        schemas.push(name);
1141        Ok(())
1142    }
1143
1144    /// Drops a schema namespace.
1145    pub fn drop_schema(&self, name: &str) -> Result<(), CatalogError> {
1146        let mut schemas = self.schemas.write();
1147        if let Some(pos) = schemas.iter().position(|s| s == name) {
1148            schemas.remove(pos);
1149            Ok(())
1150        } else {
1151            Err(CatalogError::SchemaNotFound(name.to_string()))
1152        }
1153    }
1154
1155    // --- ALTER operations ---
1156
1157    /// Adds a property to an existing node type.
1158    pub fn alter_node_type_add_property(
1159        &self,
1160        type_name: &str,
1161        property: TypedProperty,
1162    ) -> Result<(), CatalogError> {
1163        let mut types = self.node_types.write();
1164        let def = types
1165            .get_mut(type_name)
1166            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1167        if def.properties.iter().any(|p| p.name == property.name) {
1168            return Err(CatalogError::TypeAlreadyExists(format!(
1169                "property {} on {}",
1170                property.name, type_name
1171            )));
1172        }
1173        def.properties.push(property);
1174        Ok(())
1175    }
1176
1177    /// Drops a property from an existing node type.
1178    pub fn alter_node_type_drop_property(
1179        &self,
1180        type_name: &str,
1181        property_name: &str,
1182    ) -> Result<(), CatalogError> {
1183        let mut types = self.node_types.write();
1184        let def = types
1185            .get_mut(type_name)
1186            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1187        let len_before = def.properties.len();
1188        def.properties.retain(|p| p.name != property_name);
1189        if def.properties.len() == len_before {
1190            return Err(CatalogError::TypeNotFound(format!(
1191                "property {} on {}",
1192                property_name, type_name
1193            )));
1194        }
1195        Ok(())
1196    }
1197
1198    /// Adds a property to an existing edge type.
1199    pub fn alter_edge_type_add_property(
1200        &self,
1201        type_name: &str,
1202        property: TypedProperty,
1203    ) -> Result<(), CatalogError> {
1204        let mut types = self.edge_types.write();
1205        let def = types
1206            .get_mut(type_name)
1207            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1208        if def.properties.iter().any(|p| p.name == property.name) {
1209            return Err(CatalogError::TypeAlreadyExists(format!(
1210                "property {} on {}",
1211                property.name, type_name
1212            )));
1213        }
1214        def.properties.push(property);
1215        Ok(())
1216    }
1217
1218    /// Drops a property from an existing edge type.
1219    pub fn alter_edge_type_drop_property(
1220        &self,
1221        type_name: &str,
1222        property_name: &str,
1223    ) -> Result<(), CatalogError> {
1224        let mut types = self.edge_types.write();
1225        let def = types
1226            .get_mut(type_name)
1227            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1228        let len_before = def.properties.len();
1229        def.properties.retain(|p| p.name != property_name);
1230        if def.properties.len() == len_before {
1231            return Err(CatalogError::TypeNotFound(format!(
1232                "property {} on {}",
1233                property_name, type_name
1234            )));
1235        }
1236        Ok(())
1237    }
1238
1239    /// Adds a node type to a graph type.
1240    pub fn alter_graph_type_add_node_type(
1241        &self,
1242        graph_type_name: &str,
1243        node_type: String,
1244    ) -> Result<(), CatalogError> {
1245        let mut types = self.graph_types.write();
1246        let def = types
1247            .get_mut(graph_type_name)
1248            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1249        if !def.allowed_node_types.contains(&node_type) {
1250            def.allowed_node_types.push(node_type);
1251        }
1252        Ok(())
1253    }
1254
1255    /// Drops a node type from a graph type.
1256    pub fn alter_graph_type_drop_node_type(
1257        &self,
1258        graph_type_name: &str,
1259        node_type: &str,
1260    ) -> Result<(), CatalogError> {
1261        let mut types = self.graph_types.write();
1262        let def = types
1263            .get_mut(graph_type_name)
1264            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1265        def.allowed_node_types.retain(|t| t != node_type);
1266        Ok(())
1267    }
1268
1269    /// Adds an edge type to a graph type.
1270    pub fn alter_graph_type_add_edge_type(
1271        &self,
1272        graph_type_name: &str,
1273        edge_type: String,
1274    ) -> Result<(), CatalogError> {
1275        let mut types = self.graph_types.write();
1276        let def = types
1277            .get_mut(graph_type_name)
1278            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1279        if !def.allowed_edge_types.contains(&edge_type) {
1280            def.allowed_edge_types.push(edge_type);
1281        }
1282        Ok(())
1283    }
1284
1285    /// Drops an edge type from a graph type.
1286    pub fn alter_graph_type_drop_edge_type(
1287        &self,
1288        graph_type_name: &str,
1289        edge_type: &str,
1290    ) -> Result<(), CatalogError> {
1291        let mut types = self.graph_types.write();
1292        let def = types
1293            .get_mut(graph_type_name)
1294            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1295        def.allowed_edge_types.retain(|t| t != edge_type);
1296        Ok(())
1297    }
1298
1299    // --- Procedure operations ---
1300
1301    /// Registers a stored procedure.
1302    pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
1303        let mut procs = self.procedures.write();
1304        if procs.contains_key(&def.name) {
1305            return Err(CatalogError::TypeAlreadyExists(def.name.clone()));
1306        }
1307        procs.insert(def.name.clone(), def);
1308        Ok(())
1309    }
1310
1311    /// Replaces or creates a stored procedure.
1312    pub fn replace_procedure(&self, def: ProcedureDefinition) {
1313        self.procedures.write().insert(def.name.clone(), def);
1314    }
1315
1316    /// Drops a stored procedure.
1317    pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
1318        let mut procs = self.procedures.write();
1319        if procs.remove(name).is_none() {
1320            return Err(CatalogError::TypeNotFound(name.to_string()));
1321        }
1322        Ok(())
1323    }
1324
1325    /// Gets a stored procedure by name.
1326    pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
1327        self.procedures.read().get(name).cloned()
1328    }
1329
1330    fn add_unique_constraint(
1331        &self,
1332        label: LabelId,
1333        property_key: PropertyKeyId,
1334    ) -> Result<(), CatalogError> {
1335        let mut constraints = self.unique_constraints.write();
1336        let key = (label, property_key);
1337        if constraints.contains_key(&key) {
1338            return Err(CatalogError::ConstraintAlreadyExists);
1339        }
1340        constraints.insert(key, ());
1341        Ok(())
1342    }
1343
1344    fn add_required_property(
1345        &self,
1346        label: LabelId,
1347        property_key: PropertyKeyId,
1348    ) -> Result<(), CatalogError> {
1349        let mut required = self.required_properties.write();
1350        let key = (label, property_key);
1351        if required.contains_key(&key) {
1352            return Err(CatalogError::ConstraintAlreadyExists);
1353        }
1354        required.insert(key, ());
1355        Ok(())
1356    }
1357
1358    fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1359        self.required_properties
1360            .read()
1361            .contains_key(&(label, property_key))
1362    }
1363
1364    fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1365        self.unique_constraints
1366            .read()
1367            .contains_key(&(label, property_key))
1368    }
1369}
1370
1371// === Errors ===
1372
1373/// Catalog-related errors.
1374#[derive(Debug, Clone, PartialEq, Eq)]
1375pub enum CatalogError {
1376    /// Schema constraints are not enabled.
1377    SchemaNotEnabled,
1378    /// The constraint already exists.
1379    ConstraintAlreadyExists,
1380    /// The label does not exist.
1381    LabelNotFound(String),
1382    /// The property key does not exist.
1383    PropertyKeyNotFound(String),
1384    /// The edge type does not exist.
1385    EdgeTypeNotFound(String),
1386    /// The index does not exist.
1387    IndexNotFound(IndexId),
1388    /// A type with this name already exists.
1389    TypeAlreadyExists(String),
1390    /// No type with this name exists.
1391    TypeNotFound(String),
1392    /// A schema with this name already exists.
1393    SchemaAlreadyExists(String),
1394    /// No schema with this name exists.
1395    SchemaNotFound(String),
1396}
1397
1398impl std::fmt::Display for CatalogError {
1399    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1400        match self {
1401            Self::SchemaNotEnabled => write!(f, "Schema constraints are not enabled"),
1402            Self::ConstraintAlreadyExists => write!(f, "Constraint already exists"),
1403            Self::LabelNotFound(name) => write!(f, "Label not found: {name}"),
1404            Self::PropertyKeyNotFound(name) => write!(f, "Property key not found: {name}"),
1405            Self::EdgeTypeNotFound(name) => write!(f, "Edge type not found: {name}"),
1406            Self::IndexNotFound(id) => write!(f, "Index not found: {id}"),
1407            Self::TypeAlreadyExists(name) => write!(f, "Type already exists: {name}"),
1408            Self::TypeNotFound(name) => write!(f, "Type not found: {name}"),
1409            Self::SchemaAlreadyExists(name) => write!(f, "Schema already exists: {name}"),
1410            Self::SchemaNotFound(name) => write!(f, "Schema not found: {name}"),
1411        }
1412    }
1413}
1414
1415impl std::error::Error for CatalogError {}
1416
1417// === Constraint Validator ===
1418
1419use grafeo_core::execution::operators::ConstraintValidator;
1420use grafeo_core::execution::operators::OperatorError;
1421
1422/// Validates schema constraints during mutation operations using the Catalog.
1423///
1424/// Checks type definitions, NOT NULL constraints, and UNIQUE constraints
1425/// against registered node/edge type definitions.
1426pub struct CatalogConstraintValidator {
1427    catalog: Arc<Catalog>,
1428}
1429
1430impl CatalogConstraintValidator {
1431    /// Creates a new validator wrapping the given catalog.
1432    pub fn new(catalog: Arc<Catalog>) -> Self {
1433        Self { catalog }
1434    }
1435}
1436
1437impl ConstraintValidator for CatalogConstraintValidator {
1438    fn validate_node_property(
1439        &self,
1440        labels: &[String],
1441        key: &str,
1442        value: &Value,
1443    ) -> Result<(), OperatorError> {
1444        for label in labels {
1445            if let Some(type_def) = self.catalog.get_node_type(label)
1446                && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1447            {
1448                // Check NOT NULL
1449                if !typed_prop.nullable && *value == Value::Null {
1450                    return Err(OperatorError::ConstraintViolation(format!(
1451                        "property '{key}' on :{label} is NOT NULL, cannot set to null"
1452                    )));
1453                }
1454                // Check type compatibility
1455                if *value != Value::Null && !typed_prop.data_type.matches(value) {
1456                    return Err(OperatorError::ConstraintViolation(format!(
1457                        "property '{key}' on :{label} expects {:?}, got {:?}",
1458                        typed_prop.data_type, value
1459                    )));
1460                }
1461            }
1462        }
1463        Ok(())
1464    }
1465
1466    fn validate_node_complete(
1467        &self,
1468        labels: &[String],
1469        properties: &[(String, Value)],
1470    ) -> Result<(), OperatorError> {
1471        let prop_names: std::collections::HashSet<&str> =
1472            properties.iter().map(|(n, _)| n.as_str()).collect();
1473
1474        for label in labels {
1475            if let Some(type_def) = self.catalog.get_node_type(label) {
1476                // Check that all NOT NULL properties are present
1477                for typed_prop in &type_def.properties {
1478                    if !typed_prop.nullable
1479                        && typed_prop.default_value.is_none()
1480                        && !prop_names.contains(typed_prop.name.as_str())
1481                    {
1482                        return Err(OperatorError::ConstraintViolation(format!(
1483                            "missing required property '{}' on :{label}",
1484                            typed_prop.name
1485                        )));
1486                    }
1487                }
1488                // Check type-level constraints
1489                for constraint in &type_def.constraints {
1490                    match constraint {
1491                        TypeConstraint::NotNull(prop_name) => {
1492                            if !prop_names.contains(prop_name.as_str()) {
1493                                return Err(OperatorError::ConstraintViolation(format!(
1494                                    "missing required property '{prop_name}' on :{label} (NOT NULL constraint)"
1495                                )));
1496                            }
1497                        }
1498                        TypeConstraint::PrimaryKey(key_props) => {
1499                            for pk in key_props {
1500                                if !prop_names.contains(pk.as_str()) {
1501                                    return Err(OperatorError::ConstraintViolation(format!(
1502                                        "missing primary key property '{pk}' on :{label}"
1503                                    )));
1504                                }
1505                            }
1506                        }
1507                        _ => {}
1508                    }
1509                }
1510            }
1511        }
1512        Ok(())
1513    }
1514
1515    fn check_unique_node_property(
1516        &self,
1517        labels: &[String],
1518        key: &str,
1519        value: &Value,
1520    ) -> Result<(), OperatorError> {
1521        // Skip uniqueness check for NULL values (NULLs are never duplicates)
1522        if *value == Value::Null {
1523            return Ok(());
1524        }
1525        for label in labels {
1526            if let Some(type_def) = self.catalog.get_node_type(label) {
1527                for constraint in &type_def.constraints {
1528                    let is_unique = match constraint {
1529                        TypeConstraint::Unique(props) => props.iter().any(|p| p == key),
1530                        TypeConstraint::PrimaryKey(props) => props.iter().any(|p| p == key),
1531                        _ => false,
1532                    };
1533                    if is_unique {
1534                        // Use the catalog's label/property ID-based check if available
1535                        let label_id = self.catalog.get_or_create_label(label);
1536                        let prop_id = self.catalog.get_or_create_property_key(key);
1537                        if self.catalog.is_property_unique(label_id, prop_id) {
1538                            // The constraint is registered, enforcement depends on
1539                            // the property index which is checked at the store level.
1540                            // For now, we mark the constraint as active.
1541                            // Full duplicate detection requires index lookup, which
1542                            // is done when the property index exists.
1543                        }
1544                    }
1545                }
1546            }
1547        }
1548        Ok(())
1549    }
1550
1551    fn validate_edge_property(
1552        &self,
1553        edge_type: &str,
1554        key: &str,
1555        value: &Value,
1556    ) -> Result<(), OperatorError> {
1557        if let Some(type_def) = self.catalog.get_edge_type_def(edge_type)
1558            && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1559        {
1560            // Check NOT NULL
1561            if !typed_prop.nullable && *value == Value::Null {
1562                return Err(OperatorError::ConstraintViolation(format!(
1563                    "property '{key}' on :{edge_type} is NOT NULL, cannot set to null"
1564                )));
1565            }
1566            // Check type compatibility
1567            if *value != Value::Null && !typed_prop.data_type.matches(value) {
1568                return Err(OperatorError::ConstraintViolation(format!(
1569                    "property '{key}' on :{edge_type} expects {:?}, got {:?}",
1570                    typed_prop.data_type, value
1571                )));
1572            }
1573        }
1574        Ok(())
1575    }
1576
1577    fn validate_edge_complete(
1578        &self,
1579        edge_type: &str,
1580        properties: &[(String, Value)],
1581    ) -> Result<(), OperatorError> {
1582        if let Some(type_def) = self.catalog.get_edge_type_def(edge_type) {
1583            let prop_names: std::collections::HashSet<&str> =
1584                properties.iter().map(|(n, _)| n.as_str()).collect();
1585
1586            for typed_prop in &type_def.properties {
1587                if !typed_prop.nullable
1588                    && typed_prop.default_value.is_none()
1589                    && !prop_names.contains(typed_prop.name.as_str())
1590                {
1591                    return Err(OperatorError::ConstraintViolation(format!(
1592                        "missing required property '{}' on :{edge_type}",
1593                        typed_prop.name
1594                    )));
1595                }
1596            }
1597        }
1598        Ok(())
1599    }
1600}
1601
1602#[cfg(test)]
1603mod tests {
1604    use super::*;
1605    use std::thread;
1606
1607    #[test]
1608    fn test_catalog_labels() {
1609        let catalog = Catalog::new();
1610
1611        // Get or create labels
1612        let person_id = catalog.get_or_create_label("Person");
1613        let company_id = catalog.get_or_create_label("Company");
1614
1615        // IDs should be different
1616        assert_ne!(person_id, company_id);
1617
1618        // Getting the same label should return the same ID
1619        assert_eq!(catalog.get_or_create_label("Person"), person_id);
1620
1621        // Should be able to look up by name
1622        assert_eq!(catalog.get_label_id("Person"), Some(person_id));
1623        assert_eq!(catalog.get_label_id("Company"), Some(company_id));
1624        assert_eq!(catalog.get_label_id("Unknown"), None);
1625
1626        // Should be able to look up by ID
1627        assert_eq!(catalog.get_label_name(person_id).as_deref(), Some("Person"));
1628        assert_eq!(
1629            catalog.get_label_name(company_id).as_deref(),
1630            Some("Company")
1631        );
1632
1633        // Count should be correct
1634        assert_eq!(catalog.label_count(), 2);
1635    }
1636
1637    #[test]
1638    fn test_catalog_property_keys() {
1639        let catalog = Catalog::new();
1640
1641        let name_id = catalog.get_or_create_property_key("name");
1642        let age_id = catalog.get_or_create_property_key("age");
1643
1644        assert_ne!(name_id, age_id);
1645        assert_eq!(catalog.get_or_create_property_key("name"), name_id);
1646        assert_eq!(catalog.get_property_key_id("name"), Some(name_id));
1647        assert_eq!(
1648            catalog.get_property_key_name(name_id).as_deref(),
1649            Some("name")
1650        );
1651        assert_eq!(catalog.property_key_count(), 2);
1652    }
1653
1654    #[test]
1655    fn test_catalog_edge_types() {
1656        let catalog = Catalog::new();
1657
1658        let knows_id = catalog.get_or_create_edge_type("KNOWS");
1659        let works_at_id = catalog.get_or_create_edge_type("WORKS_AT");
1660
1661        assert_ne!(knows_id, works_at_id);
1662        assert_eq!(catalog.get_or_create_edge_type("KNOWS"), knows_id);
1663        assert_eq!(catalog.get_edge_type_id("KNOWS"), Some(knows_id));
1664        assert_eq!(
1665            catalog.get_edge_type_name(knows_id).as_deref(),
1666            Some("KNOWS")
1667        );
1668        assert_eq!(catalog.edge_type_count(), 2);
1669    }
1670
1671    #[test]
1672    fn test_catalog_indexes() {
1673        let catalog = Catalog::new();
1674
1675        let person_id = catalog.get_or_create_label("Person");
1676        let name_id = catalog.get_or_create_property_key("name");
1677        let age_id = catalog.get_or_create_property_key("age");
1678
1679        // Create indexes
1680        let idx1 = catalog.create_index(person_id, name_id, IndexType::Hash);
1681        let idx2 = catalog.create_index(person_id, age_id, IndexType::BTree);
1682
1683        assert_ne!(idx1, idx2);
1684        assert_eq!(catalog.index_count(), 2);
1685
1686        // Look up by label
1687        let label_indexes = catalog.indexes_for_label(person_id);
1688        assert_eq!(label_indexes.len(), 2);
1689        assert!(label_indexes.contains(&idx1));
1690        assert!(label_indexes.contains(&idx2));
1691
1692        // Look up by label and property
1693        let name_indexes = catalog.indexes_for_label_property(person_id, name_id);
1694        assert_eq!(name_indexes.len(), 1);
1695        assert_eq!(name_indexes[0], idx1);
1696
1697        // Get definition
1698        let def = catalog.get_index(idx1).unwrap();
1699        assert_eq!(def.label, person_id);
1700        assert_eq!(def.property_key, name_id);
1701        assert_eq!(def.index_type, IndexType::Hash);
1702
1703        // Drop index
1704        assert!(catalog.drop_index(idx1));
1705        assert_eq!(catalog.index_count(), 1);
1706        assert!(catalog.get_index(idx1).is_none());
1707        assert_eq!(catalog.indexes_for_label(person_id).len(), 1);
1708    }
1709
1710    #[test]
1711    fn test_catalog_schema_constraints() {
1712        let catalog = Catalog::with_schema();
1713
1714        let person_id = catalog.get_or_create_label("Person");
1715        let email_id = catalog.get_or_create_property_key("email");
1716        let name_id = catalog.get_or_create_property_key("name");
1717
1718        // Add constraints
1719        assert!(catalog.add_unique_constraint(person_id, email_id).is_ok());
1720        assert!(catalog.add_required_property(person_id, name_id).is_ok());
1721
1722        // Check constraints
1723        assert!(catalog.is_property_unique(person_id, email_id));
1724        assert!(!catalog.is_property_unique(person_id, name_id));
1725        assert!(catalog.is_property_required(person_id, name_id));
1726        assert!(!catalog.is_property_required(person_id, email_id));
1727
1728        // Duplicate constraint should fail
1729        assert_eq!(
1730            catalog.add_unique_constraint(person_id, email_id),
1731            Err(CatalogError::ConstraintAlreadyExists)
1732        );
1733    }
1734
1735    #[test]
1736    fn test_catalog_schema_always_enabled() {
1737        // Catalog::new() always enables schema
1738        let catalog = Catalog::new();
1739        assert!(catalog.has_schema());
1740
1741        let person_id = catalog.get_or_create_label("Person");
1742        let email_id = catalog.get_or_create_property_key("email");
1743
1744        // Should succeed with schema enabled
1745        assert_eq!(catalog.add_unique_constraint(person_id, email_id), Ok(()));
1746    }
1747
1748    // === Additional tests for comprehensive coverage ===
1749
1750    #[test]
1751    fn test_catalog_default() {
1752        let catalog = Catalog::default();
1753        assert!(catalog.has_schema());
1754        assert_eq!(catalog.label_count(), 0);
1755        assert_eq!(catalog.property_key_count(), 0);
1756        assert_eq!(catalog.edge_type_count(), 0);
1757        assert_eq!(catalog.index_count(), 0);
1758    }
1759
1760    #[test]
1761    fn test_catalog_all_labels() {
1762        let catalog = Catalog::new();
1763
1764        catalog.get_or_create_label("Person");
1765        catalog.get_or_create_label("Company");
1766        catalog.get_or_create_label("Product");
1767
1768        let all = catalog.all_labels();
1769        assert_eq!(all.len(), 3);
1770        assert!(all.iter().any(|l| l.as_ref() == "Person"));
1771        assert!(all.iter().any(|l| l.as_ref() == "Company"));
1772        assert!(all.iter().any(|l| l.as_ref() == "Product"));
1773    }
1774
1775    #[test]
1776    fn test_catalog_all_property_keys() {
1777        let catalog = Catalog::new();
1778
1779        catalog.get_or_create_property_key("name");
1780        catalog.get_or_create_property_key("age");
1781        catalog.get_or_create_property_key("email");
1782
1783        let all = catalog.all_property_keys();
1784        assert_eq!(all.len(), 3);
1785        assert!(all.iter().any(|k| k.as_ref() == "name"));
1786        assert!(all.iter().any(|k| k.as_ref() == "age"));
1787        assert!(all.iter().any(|k| k.as_ref() == "email"));
1788    }
1789
1790    #[test]
1791    fn test_catalog_all_edge_types() {
1792        let catalog = Catalog::new();
1793
1794        catalog.get_or_create_edge_type("KNOWS");
1795        catalog.get_or_create_edge_type("WORKS_AT");
1796        catalog.get_or_create_edge_type("LIVES_IN");
1797
1798        let all = catalog.all_edge_types();
1799        assert_eq!(all.len(), 3);
1800        assert!(all.iter().any(|t| t.as_ref() == "KNOWS"));
1801        assert!(all.iter().any(|t| t.as_ref() == "WORKS_AT"));
1802        assert!(all.iter().any(|t| t.as_ref() == "LIVES_IN"));
1803    }
1804
1805    #[test]
1806    fn test_catalog_invalid_id_lookup() {
1807        let catalog = Catalog::new();
1808
1809        // Create one label to ensure IDs are allocated
1810        let _ = catalog.get_or_create_label("Person");
1811
1812        // Try to look up non-existent IDs
1813        let invalid_label = LabelId::new(999);
1814        let invalid_property = PropertyKeyId::new(999);
1815        let invalid_edge_type = EdgeTypeId::new(999);
1816        let invalid_index = IndexId::new(999);
1817
1818        assert!(catalog.get_label_name(invalid_label).is_none());
1819        assert!(catalog.get_property_key_name(invalid_property).is_none());
1820        assert!(catalog.get_edge_type_name(invalid_edge_type).is_none());
1821        assert!(catalog.get_index(invalid_index).is_none());
1822    }
1823
1824    #[test]
1825    fn test_catalog_drop_nonexistent_index() {
1826        let catalog = Catalog::new();
1827        let invalid_index = IndexId::new(999);
1828        assert!(!catalog.drop_index(invalid_index));
1829    }
1830
1831    #[test]
1832    fn test_catalog_indexes_for_nonexistent_label() {
1833        let catalog = Catalog::new();
1834        let invalid_label = LabelId::new(999);
1835        let invalid_property = PropertyKeyId::new(999);
1836
1837        assert!(catalog.indexes_for_label(invalid_label).is_empty());
1838        assert!(
1839            catalog
1840                .indexes_for_label_property(invalid_label, invalid_property)
1841                .is_empty()
1842        );
1843    }
1844
1845    #[test]
1846    fn test_catalog_multiple_indexes_same_property() {
1847        let catalog = Catalog::new();
1848
1849        let person_id = catalog.get_or_create_label("Person");
1850        let name_id = catalog.get_or_create_property_key("name");
1851
1852        // Create multiple indexes on the same property with different types
1853        let hash_idx = catalog.create_index(person_id, name_id, IndexType::Hash);
1854        let btree_idx = catalog.create_index(person_id, name_id, IndexType::BTree);
1855        let fulltext_idx = catalog.create_index(person_id, name_id, IndexType::FullText);
1856
1857        assert_eq!(catalog.index_count(), 3);
1858
1859        let indexes = catalog.indexes_for_label_property(person_id, name_id);
1860        assert_eq!(indexes.len(), 3);
1861        assert!(indexes.contains(&hash_idx));
1862        assert!(indexes.contains(&btree_idx));
1863        assert!(indexes.contains(&fulltext_idx));
1864
1865        // Verify each has the correct type
1866        assert_eq!(
1867            catalog.get_index(hash_idx).unwrap().index_type,
1868            IndexType::Hash
1869        );
1870        assert_eq!(
1871            catalog.get_index(btree_idx).unwrap().index_type,
1872            IndexType::BTree
1873        );
1874        assert_eq!(
1875            catalog.get_index(fulltext_idx).unwrap().index_type,
1876            IndexType::FullText
1877        );
1878    }
1879
1880    #[test]
1881    fn test_catalog_schema_required_property_duplicate() {
1882        let catalog = Catalog::with_schema();
1883
1884        let person_id = catalog.get_or_create_label("Person");
1885        let name_id = catalog.get_or_create_property_key("name");
1886
1887        // First should succeed
1888        assert!(catalog.add_required_property(person_id, name_id).is_ok());
1889
1890        // Duplicate should fail
1891        assert_eq!(
1892            catalog.add_required_property(person_id, name_id),
1893            Err(CatalogError::ConstraintAlreadyExists)
1894        );
1895    }
1896
1897    #[test]
1898    fn test_catalog_schema_check_without_constraints() {
1899        let catalog = Catalog::new();
1900
1901        let person_id = catalog.get_or_create_label("Person");
1902        let name_id = catalog.get_or_create_property_key("name");
1903
1904        // Without schema enabled, these should return false
1905        assert!(!catalog.is_property_unique(person_id, name_id));
1906        assert!(!catalog.is_property_required(person_id, name_id));
1907    }
1908
1909    #[test]
1910    fn test_catalog_has_schema() {
1911        // Both new() and with_schema() enable schema by default
1912        let catalog = Catalog::new();
1913        assert!(catalog.has_schema());
1914
1915        let with_schema = Catalog::with_schema();
1916        assert!(with_schema.has_schema());
1917    }
1918
1919    #[test]
1920    fn test_catalog_error_display() {
1921        assert_eq!(
1922            CatalogError::SchemaNotEnabled.to_string(),
1923            "Schema constraints are not enabled"
1924        );
1925        assert_eq!(
1926            CatalogError::ConstraintAlreadyExists.to_string(),
1927            "Constraint already exists"
1928        );
1929        assert_eq!(
1930            CatalogError::LabelNotFound("Person".to_string()).to_string(),
1931            "Label not found: Person"
1932        );
1933        assert_eq!(
1934            CatalogError::PropertyKeyNotFound("name".to_string()).to_string(),
1935            "Property key not found: name"
1936        );
1937        assert_eq!(
1938            CatalogError::EdgeTypeNotFound("KNOWS".to_string()).to_string(),
1939            "Edge type not found: KNOWS"
1940        );
1941        let idx = IndexId::new(42);
1942        assert!(CatalogError::IndexNotFound(idx).to_string().contains("42"));
1943    }
1944
1945    #[test]
1946    fn test_catalog_concurrent_label_creation() {
1947        use std::sync::Arc;
1948
1949        let catalog = Arc::new(Catalog::new());
1950        let mut handles = vec![];
1951
1952        // Spawn multiple threads trying to create the same labels
1953        for i in 0..10 {
1954            let catalog = Arc::clone(&catalog);
1955            handles.push(thread::spawn(move || {
1956                let label_name = format!("Label{}", i % 3); // Only 3 unique labels
1957                catalog.get_or_create_label(&label_name)
1958            }));
1959        }
1960
1961        let mut ids: Vec<LabelId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
1962        ids.sort_by_key(|id| id.as_u32());
1963        ids.dedup();
1964
1965        // Should only have 3 unique label IDs
1966        assert_eq!(ids.len(), 3);
1967        assert_eq!(catalog.label_count(), 3);
1968    }
1969
1970    #[test]
1971    fn test_catalog_concurrent_property_key_creation() {
1972        use std::sync::Arc;
1973
1974        let catalog = Arc::new(Catalog::new());
1975        let mut handles = vec![];
1976
1977        for i in 0..10 {
1978            let catalog = Arc::clone(&catalog);
1979            handles.push(thread::spawn(move || {
1980                let key_name = format!("key{}", i % 4);
1981                catalog.get_or_create_property_key(&key_name)
1982            }));
1983        }
1984
1985        let mut ids: Vec<PropertyKeyId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
1986        ids.sort_by_key(|id| id.as_u32());
1987        ids.dedup();
1988
1989        assert_eq!(ids.len(), 4);
1990        assert_eq!(catalog.property_key_count(), 4);
1991    }
1992
1993    #[test]
1994    fn test_catalog_concurrent_index_operations() {
1995        use std::sync::Arc;
1996
1997        let catalog = Arc::new(Catalog::new());
1998        let label = catalog.get_or_create_label("Node");
1999
2000        let mut handles = vec![];
2001
2002        // Create indexes concurrently
2003        for i in 0..5 {
2004            let catalog = Arc::clone(&catalog);
2005            handles.push(thread::spawn(move || {
2006                let prop = PropertyKeyId::new(i);
2007                catalog.create_index(label, prop, IndexType::Hash)
2008            }));
2009        }
2010
2011        let ids: Vec<IndexId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2012        assert_eq!(ids.len(), 5);
2013        assert_eq!(catalog.index_count(), 5);
2014    }
2015
2016    #[test]
2017    fn test_catalog_special_characters_in_names() {
2018        let catalog = Catalog::new();
2019
2020        // Test with various special characters
2021        let label1 = catalog.get_or_create_label("Label With Spaces");
2022        let label2 = catalog.get_or_create_label("Label-With-Dashes");
2023        let label3 = catalog.get_or_create_label("Label_With_Underscores");
2024        let label4 = catalog.get_or_create_label("LabelWithUnicode\u{00E9}");
2025
2026        assert_ne!(label1, label2);
2027        assert_ne!(label2, label3);
2028        assert_ne!(label3, label4);
2029
2030        assert_eq!(
2031            catalog.get_label_name(label1).as_deref(),
2032            Some("Label With Spaces")
2033        );
2034        assert_eq!(
2035            catalog.get_label_name(label4).as_deref(),
2036            Some("LabelWithUnicode\u{00E9}")
2037        );
2038    }
2039
2040    #[test]
2041    fn test_catalog_empty_names() {
2042        let catalog = Catalog::new();
2043
2044        // Empty names should be valid (edge case)
2045        let empty_label = catalog.get_or_create_label("");
2046        let empty_prop = catalog.get_or_create_property_key("");
2047        let empty_edge = catalog.get_or_create_edge_type("");
2048
2049        assert_eq!(catalog.get_label_name(empty_label).as_deref(), Some(""));
2050        assert_eq!(
2051            catalog.get_property_key_name(empty_prop).as_deref(),
2052            Some("")
2053        );
2054        assert_eq!(catalog.get_edge_type_name(empty_edge).as_deref(), Some(""));
2055
2056        // Calling again should return same ID
2057        assert_eq!(catalog.get_or_create_label(""), empty_label);
2058    }
2059
2060    #[test]
2061    fn test_catalog_large_number_of_entries() {
2062        let catalog = Catalog::new();
2063
2064        // Create many labels
2065        for i in 0..1000 {
2066            catalog.get_or_create_label(&format!("Label{}", i));
2067        }
2068
2069        assert_eq!(catalog.label_count(), 1000);
2070
2071        // Verify we can retrieve them all
2072        let all = catalog.all_labels();
2073        assert_eq!(all.len(), 1000);
2074
2075        // Verify a specific one
2076        let id = catalog.get_label_id("Label500").unwrap();
2077        assert_eq!(catalog.get_label_name(id).as_deref(), Some("Label500"));
2078    }
2079
2080    #[test]
2081    fn test_index_definition_debug() {
2082        let def = IndexDefinition {
2083            id: IndexId::new(1),
2084            label: LabelId::new(2),
2085            property_key: PropertyKeyId::new(3),
2086            index_type: IndexType::Hash,
2087        };
2088
2089        // Should be able to debug print
2090        let debug_str = format!("{:?}", def);
2091        assert!(debug_str.contains("IndexDefinition"));
2092        assert!(debug_str.contains("Hash"));
2093    }
2094
2095    #[test]
2096    fn test_index_type_equality() {
2097        assert_eq!(IndexType::Hash, IndexType::Hash);
2098        assert_ne!(IndexType::Hash, IndexType::BTree);
2099        assert_ne!(IndexType::BTree, IndexType::FullText);
2100
2101        // Clone
2102        let t = IndexType::Hash;
2103        let t2 = t;
2104        assert_eq!(t, t2);
2105    }
2106
2107    #[test]
2108    fn test_catalog_error_equality() {
2109        assert_eq!(
2110            CatalogError::SchemaNotEnabled,
2111            CatalogError::SchemaNotEnabled
2112        );
2113        assert_eq!(
2114            CatalogError::ConstraintAlreadyExists,
2115            CatalogError::ConstraintAlreadyExists
2116        );
2117        assert_eq!(
2118            CatalogError::LabelNotFound("X".to_string()),
2119            CatalogError::LabelNotFound("X".to_string())
2120        );
2121        assert_ne!(
2122            CatalogError::LabelNotFound("X".to_string()),
2123            CatalogError::LabelNotFound("Y".to_string())
2124        );
2125    }
2126}