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