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 (untyped).
864    List,
865    /// Typed list: `LIST<element_type>` (ISO sec 4.16.9).
866    ListTyped(Box<PropertyDataType>),
867    /// Key-value map.
868    Map,
869    /// Raw bytes.
870    Bytes,
871    /// Node reference type (ISO sec 4.15.1).
872    Node,
873    /// Edge reference type (ISO sec 4.15.1).
874    Edge,
875    /// Any type (no enforcement).
876    Any,
877}
878
879impl PropertyDataType {
880    /// Parses a type name string (case-insensitive) into a `PropertyDataType`.
881    #[must_use]
882    pub fn from_type_name(name: &str) -> Self {
883        let upper = name.to_uppercase();
884        // Handle parameterized LIST<element_type>
885        if let Some(inner) = upper
886            .strip_prefix("LIST<")
887            .and_then(|s| s.strip_suffix('>'))
888        {
889            return Self::ListTyped(Box::new(Self::from_type_name(inner)));
890        }
891        match upper.as_str() {
892            "STRING" | "VARCHAR" | "TEXT" => Self::String,
893            "INT" | "INT64" | "INTEGER" | "BIGINT" => Self::Int64,
894            "FLOAT" | "FLOAT64" | "DOUBLE" | "REAL" => Self::Float64,
895            "BOOL" | "BOOLEAN" => Self::Bool,
896            "DATE" => Self::Date,
897            "TIME" => Self::Time,
898            "TIMESTAMP" | "DATETIME" => Self::Timestamp,
899            "DURATION" | "INTERVAL" => Self::Duration,
900            "LIST" | "ARRAY" => Self::List,
901            "MAP" | "RECORD" => Self::Map,
902            "BYTES" | "BINARY" | "BLOB" => Self::Bytes,
903            "NODE" => Self::Node,
904            "EDGE" | "RELATIONSHIP" => Self::Edge,
905            _ => Self::Any,
906        }
907    }
908
909    /// Checks whether a value conforms to this type.
910    #[must_use]
911    pub fn matches(&self, value: &Value) -> bool {
912        match (self, value) {
913            (Self::Any, _) | (_, Value::Null) => true,
914            (Self::String, Value::String(_)) => true,
915            (Self::Int64, Value::Int64(_)) => true,
916            (Self::Float64, Value::Float64(_)) => true,
917            (Self::Bool, Value::Bool(_)) => true,
918            (Self::Date, Value::Date(_)) => true,
919            (Self::Time, Value::Time(_)) => true,
920            (Self::Timestamp, Value::Timestamp(_)) => true,
921            (Self::Duration, Value::Duration(_)) => true,
922            (Self::List, Value::List(_)) => true,
923            (Self::ListTyped(elem_type), Value::List(items)) => {
924                items.iter().all(|item| elem_type.matches(item))
925            }
926            (Self::Bytes, Value::Bytes(_)) => true,
927            // Node/Edge reference types match Map values (graph elements are
928            // represented as maps with _id, _labels/_type, and properties)
929            (Self::Node | Self::Edge, Value::Map(_)) => true,
930            _ => false,
931        }
932    }
933}
934
935impl std::fmt::Display for PropertyDataType {
936    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
937        match self {
938            Self::String => write!(f, "STRING"),
939            Self::Int64 => write!(f, "INT64"),
940            Self::Float64 => write!(f, "FLOAT64"),
941            Self::Bool => write!(f, "BOOLEAN"),
942            Self::Date => write!(f, "DATE"),
943            Self::Time => write!(f, "TIME"),
944            Self::Timestamp => write!(f, "TIMESTAMP"),
945            Self::Duration => write!(f, "DURATION"),
946            Self::List => write!(f, "LIST"),
947            Self::ListTyped(elem) => write!(f, "LIST<{elem}>"),
948            Self::Map => write!(f, "MAP"),
949            Self::Bytes => write!(f, "BYTES"),
950            Self::Node => write!(f, "NODE"),
951            Self::Edge => write!(f, "EDGE"),
952            Self::Any => write!(f, "ANY"),
953        }
954    }
955}
956
957/// A typed property within a node or edge type definition.
958#[derive(Debug, Clone)]
959pub struct TypedProperty {
960    /// Property name.
961    pub name: String,
962    /// Expected data type.
963    pub data_type: PropertyDataType,
964    /// Whether NULL values are allowed.
965    pub nullable: bool,
966    /// Default value (used when property is not explicitly set).
967    pub default_value: Option<Value>,
968}
969
970/// A constraint on a node or edge type.
971#[derive(Debug, Clone)]
972pub enum TypeConstraint {
973    /// Primary key (implies UNIQUE + NOT NULL).
974    PrimaryKey(Vec<String>),
975    /// Uniqueness constraint on one or more properties.
976    Unique(Vec<String>),
977    /// NOT NULL constraint on a single property.
978    NotNull(String),
979    /// CHECK constraint with a named expression string.
980    Check {
981        /// Optional constraint name.
982        name: Option<String>,
983        /// Expression (stored as string for now).
984        expression: String,
985    },
986}
987
988/// Definition of a node type (label schema).
989#[derive(Debug, Clone)]
990pub struct NodeTypeDefinition {
991    /// Type name (corresponds to a label).
992    pub name: String,
993    /// Typed property definitions.
994    pub properties: Vec<TypedProperty>,
995    /// Type-level constraints.
996    pub constraints: Vec<TypeConstraint>,
997}
998
999/// Definition of an edge type (relationship type schema).
1000#[derive(Debug, Clone)]
1001pub struct EdgeTypeDefinition {
1002    /// Type name (corresponds to an edge type / relationship type).
1003    pub name: String,
1004    /// Typed property definitions.
1005    pub properties: Vec<TypedProperty>,
1006    /// Type-level constraints.
1007    pub constraints: Vec<TypeConstraint>,
1008}
1009
1010/// Definition of a graph type (constrains which node/edge types a graph allows).
1011#[derive(Debug, Clone)]
1012pub struct GraphTypeDefinition {
1013    /// Graph type name.
1014    pub name: String,
1015    /// Allowed node types (empty = open).
1016    pub allowed_node_types: Vec<String>,
1017    /// Allowed edge types (empty = open).
1018    pub allowed_edge_types: Vec<String>,
1019    /// Whether unlisted types are permitted.
1020    pub open: bool,
1021}
1022
1023/// Definition of a stored procedure.
1024#[derive(Debug, Clone)]
1025pub struct ProcedureDefinition {
1026    /// Procedure name.
1027    pub name: String,
1028    /// Parameter definitions: (name, type).
1029    pub params: Vec<(String, String)>,
1030    /// Return column definitions: (name, type).
1031    pub returns: Vec<(String, String)>,
1032    /// Raw GQL query body.
1033    pub body: String,
1034}
1035
1036// === Schema Catalog ===
1037
1038/// Schema constraints and type definitions.
1039pub struct SchemaCatalog {
1040    /// Properties that must be unique for a given label.
1041    unique_constraints: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1042    /// Properties that are required (NOT NULL) for a given label.
1043    required_properties: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1044    /// Registered node type definitions.
1045    node_types: RwLock<HashMap<String, NodeTypeDefinition>>,
1046    /// Registered edge type definitions.
1047    edge_types: RwLock<HashMap<String, EdgeTypeDefinition>>,
1048    /// Registered graph type definitions.
1049    graph_types: RwLock<HashMap<String, GraphTypeDefinition>>,
1050    /// Schema namespaces.
1051    schemas: RwLock<Vec<String>>,
1052    /// Graph instance to graph type bindings.
1053    graph_type_bindings: RwLock<HashMap<String, String>>,
1054    /// Stored procedure definitions.
1055    procedures: RwLock<HashMap<String, ProcedureDefinition>>,
1056}
1057
1058impl SchemaCatalog {
1059    fn new() -> Self {
1060        Self {
1061            unique_constraints: RwLock::new(HashSet::new()),
1062            required_properties: RwLock::new(HashSet::new()),
1063            node_types: RwLock::new(HashMap::new()),
1064            edge_types: RwLock::new(HashMap::new()),
1065            graph_types: RwLock::new(HashMap::new()),
1066            schemas: RwLock::new(Vec::new()),
1067            graph_type_bindings: RwLock::new(HashMap::new()),
1068            procedures: RwLock::new(HashMap::new()),
1069        }
1070    }
1071
1072    // --- Node type operations ---
1073
1074    /// Registers a new node type definition.
1075    pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
1076        let mut types = self.node_types.write();
1077        if types.contains_key(&def.name) {
1078            return Err(CatalogError::TypeAlreadyExists(def.name));
1079        }
1080        types.insert(def.name.clone(), def);
1081        Ok(())
1082    }
1083
1084    /// Registers or replaces a node type definition.
1085    pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
1086        self.node_types.write().insert(def.name.clone(), def);
1087    }
1088
1089    /// Drops a node type definition by name.
1090    pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
1091        let mut types = self.node_types.write();
1092        if types.remove(name).is_none() {
1093            return Err(CatalogError::TypeNotFound(name.to_string()));
1094        }
1095        Ok(())
1096    }
1097
1098    /// Gets a node type definition by name.
1099    #[must_use]
1100    pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1101        self.node_types.read().get(name).cloned()
1102    }
1103
1104    /// Returns all registered node type names.
1105    #[must_use]
1106    pub fn all_node_types(&self) -> Vec<String> {
1107        self.node_types.read().keys().cloned().collect()
1108    }
1109
1110    // --- Edge type operations ---
1111
1112    /// Registers a new edge type definition.
1113    pub fn register_edge_type(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
1114        let mut types = self.edge_types.write();
1115        if types.contains_key(&def.name) {
1116            return Err(CatalogError::TypeAlreadyExists(def.name));
1117        }
1118        types.insert(def.name.clone(), def);
1119        Ok(())
1120    }
1121
1122    /// Registers or replaces an edge type definition.
1123    pub fn register_or_replace_edge_type(&self, def: EdgeTypeDefinition) {
1124        self.edge_types.write().insert(def.name.clone(), def);
1125    }
1126
1127    /// Drops an edge type definition by name.
1128    pub fn drop_edge_type(&self, name: &str) -> Result<(), CatalogError> {
1129        let mut types = self.edge_types.write();
1130        if types.remove(name).is_none() {
1131            return Err(CatalogError::TypeNotFound(name.to_string()));
1132        }
1133        Ok(())
1134    }
1135
1136    /// Gets an edge type definition by name.
1137    #[must_use]
1138    pub fn get_edge_type(&self, name: &str) -> Option<EdgeTypeDefinition> {
1139        self.edge_types.read().get(name).cloned()
1140    }
1141
1142    /// Returns all registered edge type names.
1143    #[must_use]
1144    pub fn all_edge_types(&self) -> Vec<String> {
1145        self.edge_types.read().keys().cloned().collect()
1146    }
1147
1148    // --- Graph type operations ---
1149
1150    /// Registers a new graph type definition.
1151    pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
1152        let mut types = self.graph_types.write();
1153        if types.contains_key(&def.name) {
1154            return Err(CatalogError::TypeAlreadyExists(def.name));
1155        }
1156        types.insert(def.name.clone(), def);
1157        Ok(())
1158    }
1159
1160    /// Drops a graph type definition by name.
1161    pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
1162        let mut types = self.graph_types.write();
1163        if types.remove(name).is_none() {
1164            return Err(CatalogError::TypeNotFound(name.to_string()));
1165        }
1166        Ok(())
1167    }
1168
1169    /// Gets a graph type definition by name.
1170    #[must_use]
1171    pub fn get_graph_type(&self, name: &str) -> Option<GraphTypeDefinition> {
1172        self.graph_types.read().get(name).cloned()
1173    }
1174
1175    // --- Schema namespace operations ---
1176
1177    /// Registers a schema namespace.
1178    pub fn register_schema(&self, name: String) -> Result<(), CatalogError> {
1179        let mut schemas = self.schemas.write();
1180        if schemas.contains(&name) {
1181            return Err(CatalogError::SchemaAlreadyExists(name));
1182        }
1183        schemas.push(name);
1184        Ok(())
1185    }
1186
1187    /// Drops a schema namespace.
1188    pub fn drop_schema(&self, name: &str) -> Result<(), CatalogError> {
1189        let mut schemas = self.schemas.write();
1190        if let Some(pos) = schemas.iter().position(|s| s == name) {
1191            schemas.remove(pos);
1192            Ok(())
1193        } else {
1194            Err(CatalogError::SchemaNotFound(name.to_string()))
1195        }
1196    }
1197
1198    // --- ALTER operations ---
1199
1200    /// Adds a property to an existing node type.
1201    pub fn alter_node_type_add_property(
1202        &self,
1203        type_name: &str,
1204        property: TypedProperty,
1205    ) -> Result<(), CatalogError> {
1206        let mut types = self.node_types.write();
1207        let def = types
1208            .get_mut(type_name)
1209            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1210        if def.properties.iter().any(|p| p.name == property.name) {
1211            return Err(CatalogError::TypeAlreadyExists(format!(
1212                "property {} on {}",
1213                property.name, type_name
1214            )));
1215        }
1216        def.properties.push(property);
1217        Ok(())
1218    }
1219
1220    /// Drops a property from an existing node type.
1221    pub fn alter_node_type_drop_property(
1222        &self,
1223        type_name: &str,
1224        property_name: &str,
1225    ) -> Result<(), CatalogError> {
1226        let mut types = self.node_types.write();
1227        let def = types
1228            .get_mut(type_name)
1229            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1230        let len_before = def.properties.len();
1231        def.properties.retain(|p| p.name != property_name);
1232        if def.properties.len() == len_before {
1233            return Err(CatalogError::TypeNotFound(format!(
1234                "property {} on {}",
1235                property_name, type_name
1236            )));
1237        }
1238        Ok(())
1239    }
1240
1241    /// Adds a property to an existing edge type.
1242    pub fn alter_edge_type_add_property(
1243        &self,
1244        type_name: &str,
1245        property: TypedProperty,
1246    ) -> Result<(), CatalogError> {
1247        let mut types = self.edge_types.write();
1248        let def = types
1249            .get_mut(type_name)
1250            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1251        if def.properties.iter().any(|p| p.name == property.name) {
1252            return Err(CatalogError::TypeAlreadyExists(format!(
1253                "property {} on {}",
1254                property.name, type_name
1255            )));
1256        }
1257        def.properties.push(property);
1258        Ok(())
1259    }
1260
1261    /// Drops a property from an existing edge type.
1262    pub fn alter_edge_type_drop_property(
1263        &self,
1264        type_name: &str,
1265        property_name: &str,
1266    ) -> Result<(), CatalogError> {
1267        let mut types = self.edge_types.write();
1268        let def = types
1269            .get_mut(type_name)
1270            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1271        let len_before = def.properties.len();
1272        def.properties.retain(|p| p.name != property_name);
1273        if def.properties.len() == len_before {
1274            return Err(CatalogError::TypeNotFound(format!(
1275                "property {} on {}",
1276                property_name, type_name
1277            )));
1278        }
1279        Ok(())
1280    }
1281
1282    /// Adds a node type to a graph type.
1283    pub fn alter_graph_type_add_node_type(
1284        &self,
1285        graph_type_name: &str,
1286        node_type: String,
1287    ) -> Result<(), CatalogError> {
1288        let mut types = self.graph_types.write();
1289        let def = types
1290            .get_mut(graph_type_name)
1291            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1292        if !def.allowed_node_types.contains(&node_type) {
1293            def.allowed_node_types.push(node_type);
1294        }
1295        Ok(())
1296    }
1297
1298    /// Drops a node type from a graph type.
1299    pub fn alter_graph_type_drop_node_type(
1300        &self,
1301        graph_type_name: &str,
1302        node_type: &str,
1303    ) -> Result<(), CatalogError> {
1304        let mut types = self.graph_types.write();
1305        let def = types
1306            .get_mut(graph_type_name)
1307            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1308        def.allowed_node_types.retain(|t| t != node_type);
1309        Ok(())
1310    }
1311
1312    /// Adds an edge type to a graph type.
1313    pub fn alter_graph_type_add_edge_type(
1314        &self,
1315        graph_type_name: &str,
1316        edge_type: String,
1317    ) -> Result<(), CatalogError> {
1318        let mut types = self.graph_types.write();
1319        let def = types
1320            .get_mut(graph_type_name)
1321            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1322        if !def.allowed_edge_types.contains(&edge_type) {
1323            def.allowed_edge_types.push(edge_type);
1324        }
1325        Ok(())
1326    }
1327
1328    /// Drops an edge type from a graph type.
1329    pub fn alter_graph_type_drop_edge_type(
1330        &self,
1331        graph_type_name: &str,
1332        edge_type: &str,
1333    ) -> Result<(), CatalogError> {
1334        let mut types = self.graph_types.write();
1335        let def = types
1336            .get_mut(graph_type_name)
1337            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1338        def.allowed_edge_types.retain(|t| t != edge_type);
1339        Ok(())
1340    }
1341
1342    // --- Procedure operations ---
1343
1344    /// Registers a stored procedure.
1345    pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
1346        let mut procs = self.procedures.write();
1347        if procs.contains_key(&def.name) {
1348            return Err(CatalogError::TypeAlreadyExists(def.name.clone()));
1349        }
1350        procs.insert(def.name.clone(), def);
1351        Ok(())
1352    }
1353
1354    /// Replaces or creates a stored procedure.
1355    pub fn replace_procedure(&self, def: ProcedureDefinition) {
1356        self.procedures.write().insert(def.name.clone(), def);
1357    }
1358
1359    /// Drops a stored procedure.
1360    pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
1361        let mut procs = self.procedures.write();
1362        if procs.remove(name).is_none() {
1363            return Err(CatalogError::TypeNotFound(name.to_string()));
1364        }
1365        Ok(())
1366    }
1367
1368    /// Gets a stored procedure by name.
1369    pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
1370        self.procedures.read().get(name).cloned()
1371    }
1372
1373    fn add_unique_constraint(
1374        &self,
1375        label: LabelId,
1376        property_key: PropertyKeyId,
1377    ) -> Result<(), CatalogError> {
1378        let mut constraints = self.unique_constraints.write();
1379        let key = (label, property_key);
1380        if !constraints.insert(key) {
1381            return Err(CatalogError::ConstraintAlreadyExists);
1382        }
1383        Ok(())
1384    }
1385
1386    fn add_required_property(
1387        &self,
1388        label: LabelId,
1389        property_key: PropertyKeyId,
1390    ) -> Result<(), CatalogError> {
1391        let mut required = self.required_properties.write();
1392        let key = (label, property_key);
1393        if !required.insert(key) {
1394            return Err(CatalogError::ConstraintAlreadyExists);
1395        }
1396        Ok(())
1397    }
1398
1399    fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1400        self.required_properties
1401            .read()
1402            .contains(&(label, property_key))
1403    }
1404
1405    fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1406        self.unique_constraints
1407            .read()
1408            .contains(&(label, property_key))
1409    }
1410}
1411
1412// === Errors ===
1413
1414/// Catalog-related errors.
1415#[derive(Debug, Clone, PartialEq, Eq)]
1416pub enum CatalogError {
1417    /// Schema constraints are not enabled.
1418    SchemaNotEnabled,
1419    /// The constraint already exists.
1420    ConstraintAlreadyExists,
1421    /// The label does not exist.
1422    LabelNotFound(String),
1423    /// The property key does not exist.
1424    PropertyKeyNotFound(String),
1425    /// The edge type does not exist.
1426    EdgeTypeNotFound(String),
1427    /// The index does not exist.
1428    IndexNotFound(IndexId),
1429    /// A type with this name already exists.
1430    TypeAlreadyExists(String),
1431    /// No type with this name exists.
1432    TypeNotFound(String),
1433    /// A schema with this name already exists.
1434    SchemaAlreadyExists(String),
1435    /// No schema with this name exists.
1436    SchemaNotFound(String),
1437}
1438
1439impl std::fmt::Display for CatalogError {
1440    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1441        match self {
1442            Self::SchemaNotEnabled => write!(f, "Schema constraints are not enabled"),
1443            Self::ConstraintAlreadyExists => write!(f, "Constraint already exists"),
1444            Self::LabelNotFound(name) => write!(f, "Label not found: {name}"),
1445            Self::PropertyKeyNotFound(name) => write!(f, "Property key not found: {name}"),
1446            Self::EdgeTypeNotFound(name) => write!(f, "Edge type not found: {name}"),
1447            Self::IndexNotFound(id) => write!(f, "Index not found: {id}"),
1448            Self::TypeAlreadyExists(name) => write!(f, "Type already exists: {name}"),
1449            Self::TypeNotFound(name) => write!(f, "Type not found: {name}"),
1450            Self::SchemaAlreadyExists(name) => write!(f, "Schema already exists: {name}"),
1451            Self::SchemaNotFound(name) => write!(f, "Schema not found: {name}"),
1452        }
1453    }
1454}
1455
1456impl std::error::Error for CatalogError {}
1457
1458// === Constraint Validator ===
1459
1460use grafeo_core::execution::operators::ConstraintValidator;
1461use grafeo_core::execution::operators::OperatorError;
1462
1463/// Validates schema constraints during mutation operations using the Catalog.
1464///
1465/// Checks type definitions, NOT NULL constraints, and UNIQUE constraints
1466/// against registered node/edge type definitions.
1467pub struct CatalogConstraintValidator {
1468    catalog: Arc<Catalog>,
1469}
1470
1471impl CatalogConstraintValidator {
1472    /// Creates a new validator wrapping the given catalog.
1473    pub fn new(catalog: Arc<Catalog>) -> Self {
1474        Self { catalog }
1475    }
1476}
1477
1478impl ConstraintValidator for CatalogConstraintValidator {
1479    fn validate_node_property(
1480        &self,
1481        labels: &[String],
1482        key: &str,
1483        value: &Value,
1484    ) -> Result<(), OperatorError> {
1485        for label in labels {
1486            if let Some(type_def) = self.catalog.get_node_type(label)
1487                && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1488            {
1489                // Check NOT NULL
1490                if !typed_prop.nullable && *value == Value::Null {
1491                    return Err(OperatorError::ConstraintViolation(format!(
1492                        "property '{key}' on :{label} is NOT NULL, cannot set to null"
1493                    )));
1494                }
1495                // Check type compatibility
1496                if *value != Value::Null && !typed_prop.data_type.matches(value) {
1497                    return Err(OperatorError::ConstraintViolation(format!(
1498                        "property '{key}' on :{label} expects {:?}, got {:?}",
1499                        typed_prop.data_type, value
1500                    )));
1501                }
1502            }
1503        }
1504        Ok(())
1505    }
1506
1507    fn validate_node_complete(
1508        &self,
1509        labels: &[String],
1510        properties: &[(String, Value)],
1511    ) -> Result<(), OperatorError> {
1512        let prop_names: std::collections::HashSet<&str> =
1513            properties.iter().map(|(n, _)| n.as_str()).collect();
1514
1515        for label in labels {
1516            if let Some(type_def) = self.catalog.get_node_type(label) {
1517                // Check that all NOT NULL properties are present
1518                for typed_prop in &type_def.properties {
1519                    if !typed_prop.nullable
1520                        && typed_prop.default_value.is_none()
1521                        && !prop_names.contains(typed_prop.name.as_str())
1522                    {
1523                        return Err(OperatorError::ConstraintViolation(format!(
1524                            "missing required property '{}' on :{label}",
1525                            typed_prop.name
1526                        )));
1527                    }
1528                }
1529                // Check type-level constraints
1530                for constraint in &type_def.constraints {
1531                    match constraint {
1532                        TypeConstraint::NotNull(prop_name) => {
1533                            if !prop_names.contains(prop_name.as_str()) {
1534                                return Err(OperatorError::ConstraintViolation(format!(
1535                                    "missing required property '{prop_name}' on :{label} (NOT NULL constraint)"
1536                                )));
1537                            }
1538                        }
1539                        TypeConstraint::PrimaryKey(key_props) => {
1540                            for pk in key_props {
1541                                if !prop_names.contains(pk.as_str()) {
1542                                    return Err(OperatorError::ConstraintViolation(format!(
1543                                        "missing primary key property '{pk}' on :{label}"
1544                                    )));
1545                                }
1546                            }
1547                        }
1548                        _ => {}
1549                    }
1550                }
1551            }
1552        }
1553        Ok(())
1554    }
1555
1556    fn check_unique_node_property(
1557        &self,
1558        labels: &[String],
1559        key: &str,
1560        value: &Value,
1561    ) -> Result<(), OperatorError> {
1562        // Skip uniqueness check for NULL values (NULLs are never duplicates)
1563        if *value == Value::Null {
1564            return Ok(());
1565        }
1566        for label in labels {
1567            if let Some(type_def) = self.catalog.get_node_type(label) {
1568                for constraint in &type_def.constraints {
1569                    let is_unique = match constraint {
1570                        TypeConstraint::Unique(props) => props.iter().any(|p| p == key),
1571                        TypeConstraint::PrimaryKey(props) => props.iter().any(|p| p == key),
1572                        _ => false,
1573                    };
1574                    if is_unique {
1575                        // Use the catalog's label/property ID-based check if available
1576                        let label_id = self.catalog.get_or_create_label(label);
1577                        let prop_id = self.catalog.get_or_create_property_key(key);
1578                        if self.catalog.is_property_unique(label_id, prop_id) {
1579                            // The constraint is registered, enforcement depends on
1580                            // the property index which is checked at the store level.
1581                            // For now, we mark the constraint as active.
1582                            // Full duplicate detection requires index lookup, which
1583                            // is done when the property index exists.
1584                        }
1585                    }
1586                }
1587            }
1588        }
1589        Ok(())
1590    }
1591
1592    fn validate_edge_property(
1593        &self,
1594        edge_type: &str,
1595        key: &str,
1596        value: &Value,
1597    ) -> Result<(), OperatorError> {
1598        if let Some(type_def) = self.catalog.get_edge_type_def(edge_type)
1599            && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1600        {
1601            // Check NOT NULL
1602            if !typed_prop.nullable && *value == Value::Null {
1603                return Err(OperatorError::ConstraintViolation(format!(
1604                    "property '{key}' on :{edge_type} is NOT NULL, cannot set to null"
1605                )));
1606            }
1607            // Check type compatibility
1608            if *value != Value::Null && !typed_prop.data_type.matches(value) {
1609                return Err(OperatorError::ConstraintViolation(format!(
1610                    "property '{key}' on :{edge_type} expects {:?}, got {:?}",
1611                    typed_prop.data_type, value
1612                )));
1613            }
1614        }
1615        Ok(())
1616    }
1617
1618    fn validate_edge_complete(
1619        &self,
1620        edge_type: &str,
1621        properties: &[(String, Value)],
1622    ) -> Result<(), OperatorError> {
1623        if let Some(type_def) = self.catalog.get_edge_type_def(edge_type) {
1624            let prop_names: std::collections::HashSet<&str> =
1625                properties.iter().map(|(n, _)| n.as_str()).collect();
1626
1627            for typed_prop in &type_def.properties {
1628                if !typed_prop.nullable
1629                    && typed_prop.default_value.is_none()
1630                    && !prop_names.contains(typed_prop.name.as_str())
1631                {
1632                    return Err(OperatorError::ConstraintViolation(format!(
1633                        "missing required property '{}' on :{edge_type}",
1634                        typed_prop.name
1635                    )));
1636                }
1637            }
1638        }
1639        Ok(())
1640    }
1641}
1642
1643#[cfg(test)]
1644mod tests {
1645    use super::*;
1646    use std::thread;
1647
1648    #[test]
1649    fn test_catalog_labels() {
1650        let catalog = Catalog::new();
1651
1652        // Get or create labels
1653        let person_id = catalog.get_or_create_label("Person");
1654        let company_id = catalog.get_or_create_label("Company");
1655
1656        // IDs should be different
1657        assert_ne!(person_id, company_id);
1658
1659        // Getting the same label should return the same ID
1660        assert_eq!(catalog.get_or_create_label("Person"), person_id);
1661
1662        // Should be able to look up by name
1663        assert_eq!(catalog.get_label_id("Person"), Some(person_id));
1664        assert_eq!(catalog.get_label_id("Company"), Some(company_id));
1665        assert_eq!(catalog.get_label_id("Unknown"), None);
1666
1667        // Should be able to look up by ID
1668        assert_eq!(catalog.get_label_name(person_id).as_deref(), Some("Person"));
1669        assert_eq!(
1670            catalog.get_label_name(company_id).as_deref(),
1671            Some("Company")
1672        );
1673
1674        // Count should be correct
1675        assert_eq!(catalog.label_count(), 2);
1676    }
1677
1678    #[test]
1679    fn test_catalog_property_keys() {
1680        let catalog = Catalog::new();
1681
1682        let name_id = catalog.get_or_create_property_key("name");
1683        let age_id = catalog.get_or_create_property_key("age");
1684
1685        assert_ne!(name_id, age_id);
1686        assert_eq!(catalog.get_or_create_property_key("name"), name_id);
1687        assert_eq!(catalog.get_property_key_id("name"), Some(name_id));
1688        assert_eq!(
1689            catalog.get_property_key_name(name_id).as_deref(),
1690            Some("name")
1691        );
1692        assert_eq!(catalog.property_key_count(), 2);
1693    }
1694
1695    #[test]
1696    fn test_catalog_edge_types() {
1697        let catalog = Catalog::new();
1698
1699        let knows_id = catalog.get_or_create_edge_type("KNOWS");
1700        let works_at_id = catalog.get_or_create_edge_type("WORKS_AT");
1701
1702        assert_ne!(knows_id, works_at_id);
1703        assert_eq!(catalog.get_or_create_edge_type("KNOWS"), knows_id);
1704        assert_eq!(catalog.get_edge_type_id("KNOWS"), Some(knows_id));
1705        assert_eq!(
1706            catalog.get_edge_type_name(knows_id).as_deref(),
1707            Some("KNOWS")
1708        );
1709        assert_eq!(catalog.edge_type_count(), 2);
1710    }
1711
1712    #[test]
1713    fn test_catalog_indexes() {
1714        let catalog = Catalog::new();
1715
1716        let person_id = catalog.get_or_create_label("Person");
1717        let name_id = catalog.get_or_create_property_key("name");
1718        let age_id = catalog.get_or_create_property_key("age");
1719
1720        // Create indexes
1721        let idx1 = catalog.create_index(person_id, name_id, IndexType::Hash);
1722        let idx2 = catalog.create_index(person_id, age_id, IndexType::BTree);
1723
1724        assert_ne!(idx1, idx2);
1725        assert_eq!(catalog.index_count(), 2);
1726
1727        // Look up by label
1728        let label_indexes = catalog.indexes_for_label(person_id);
1729        assert_eq!(label_indexes.len(), 2);
1730        assert!(label_indexes.contains(&idx1));
1731        assert!(label_indexes.contains(&idx2));
1732
1733        // Look up by label and property
1734        let name_indexes = catalog.indexes_for_label_property(person_id, name_id);
1735        assert_eq!(name_indexes.len(), 1);
1736        assert_eq!(name_indexes[0], idx1);
1737
1738        // Get definition
1739        let def = catalog.get_index(idx1).unwrap();
1740        assert_eq!(def.label, person_id);
1741        assert_eq!(def.property_key, name_id);
1742        assert_eq!(def.index_type, IndexType::Hash);
1743
1744        // Drop index
1745        assert!(catalog.drop_index(idx1));
1746        assert_eq!(catalog.index_count(), 1);
1747        assert!(catalog.get_index(idx1).is_none());
1748        assert_eq!(catalog.indexes_for_label(person_id).len(), 1);
1749    }
1750
1751    #[test]
1752    fn test_catalog_schema_constraints() {
1753        let catalog = Catalog::with_schema();
1754
1755        let person_id = catalog.get_or_create_label("Person");
1756        let email_id = catalog.get_or_create_property_key("email");
1757        let name_id = catalog.get_or_create_property_key("name");
1758
1759        // Add constraints
1760        assert!(catalog.add_unique_constraint(person_id, email_id).is_ok());
1761        assert!(catalog.add_required_property(person_id, name_id).is_ok());
1762
1763        // Check constraints
1764        assert!(catalog.is_property_unique(person_id, email_id));
1765        assert!(!catalog.is_property_unique(person_id, name_id));
1766        assert!(catalog.is_property_required(person_id, name_id));
1767        assert!(!catalog.is_property_required(person_id, email_id));
1768
1769        // Duplicate constraint should fail
1770        assert_eq!(
1771            catalog.add_unique_constraint(person_id, email_id),
1772            Err(CatalogError::ConstraintAlreadyExists)
1773        );
1774    }
1775
1776    #[test]
1777    fn test_catalog_schema_always_enabled() {
1778        // Catalog::new() always enables schema
1779        let catalog = Catalog::new();
1780        assert!(catalog.has_schema());
1781
1782        let person_id = catalog.get_or_create_label("Person");
1783        let email_id = catalog.get_or_create_property_key("email");
1784
1785        // Should succeed with schema enabled
1786        assert_eq!(catalog.add_unique_constraint(person_id, email_id), Ok(()));
1787    }
1788
1789    // === Additional tests for comprehensive coverage ===
1790
1791    #[test]
1792    fn test_catalog_default() {
1793        let catalog = Catalog::default();
1794        assert!(catalog.has_schema());
1795        assert_eq!(catalog.label_count(), 0);
1796        assert_eq!(catalog.property_key_count(), 0);
1797        assert_eq!(catalog.edge_type_count(), 0);
1798        assert_eq!(catalog.index_count(), 0);
1799    }
1800
1801    #[test]
1802    fn test_catalog_all_labels() {
1803        let catalog = Catalog::new();
1804
1805        catalog.get_or_create_label("Person");
1806        catalog.get_or_create_label("Company");
1807        catalog.get_or_create_label("Product");
1808
1809        let all = catalog.all_labels();
1810        assert_eq!(all.len(), 3);
1811        assert!(all.iter().any(|l| l.as_ref() == "Person"));
1812        assert!(all.iter().any(|l| l.as_ref() == "Company"));
1813        assert!(all.iter().any(|l| l.as_ref() == "Product"));
1814    }
1815
1816    #[test]
1817    fn test_catalog_all_property_keys() {
1818        let catalog = Catalog::new();
1819
1820        catalog.get_or_create_property_key("name");
1821        catalog.get_or_create_property_key("age");
1822        catalog.get_or_create_property_key("email");
1823
1824        let all = catalog.all_property_keys();
1825        assert_eq!(all.len(), 3);
1826        assert!(all.iter().any(|k| k.as_ref() == "name"));
1827        assert!(all.iter().any(|k| k.as_ref() == "age"));
1828        assert!(all.iter().any(|k| k.as_ref() == "email"));
1829    }
1830
1831    #[test]
1832    fn test_catalog_all_edge_types() {
1833        let catalog = Catalog::new();
1834
1835        catalog.get_or_create_edge_type("KNOWS");
1836        catalog.get_or_create_edge_type("WORKS_AT");
1837        catalog.get_or_create_edge_type("LIVES_IN");
1838
1839        let all = catalog.all_edge_types();
1840        assert_eq!(all.len(), 3);
1841        assert!(all.iter().any(|t| t.as_ref() == "KNOWS"));
1842        assert!(all.iter().any(|t| t.as_ref() == "WORKS_AT"));
1843        assert!(all.iter().any(|t| t.as_ref() == "LIVES_IN"));
1844    }
1845
1846    #[test]
1847    fn test_catalog_invalid_id_lookup() {
1848        let catalog = Catalog::new();
1849
1850        // Create one label to ensure IDs are allocated
1851        let _ = catalog.get_or_create_label("Person");
1852
1853        // Try to look up non-existent IDs
1854        let invalid_label = LabelId::new(999);
1855        let invalid_property = PropertyKeyId::new(999);
1856        let invalid_edge_type = EdgeTypeId::new(999);
1857        let invalid_index = IndexId::new(999);
1858
1859        assert!(catalog.get_label_name(invalid_label).is_none());
1860        assert!(catalog.get_property_key_name(invalid_property).is_none());
1861        assert!(catalog.get_edge_type_name(invalid_edge_type).is_none());
1862        assert!(catalog.get_index(invalid_index).is_none());
1863    }
1864
1865    #[test]
1866    fn test_catalog_drop_nonexistent_index() {
1867        let catalog = Catalog::new();
1868        let invalid_index = IndexId::new(999);
1869        assert!(!catalog.drop_index(invalid_index));
1870    }
1871
1872    #[test]
1873    fn test_catalog_indexes_for_nonexistent_label() {
1874        let catalog = Catalog::new();
1875        let invalid_label = LabelId::new(999);
1876        let invalid_property = PropertyKeyId::new(999);
1877
1878        assert!(catalog.indexes_for_label(invalid_label).is_empty());
1879        assert!(
1880            catalog
1881                .indexes_for_label_property(invalid_label, invalid_property)
1882                .is_empty()
1883        );
1884    }
1885
1886    #[test]
1887    fn test_catalog_multiple_indexes_same_property() {
1888        let catalog = Catalog::new();
1889
1890        let person_id = catalog.get_or_create_label("Person");
1891        let name_id = catalog.get_or_create_property_key("name");
1892
1893        // Create multiple indexes on the same property with different types
1894        let hash_idx = catalog.create_index(person_id, name_id, IndexType::Hash);
1895        let btree_idx = catalog.create_index(person_id, name_id, IndexType::BTree);
1896        let fulltext_idx = catalog.create_index(person_id, name_id, IndexType::FullText);
1897
1898        assert_eq!(catalog.index_count(), 3);
1899
1900        let indexes = catalog.indexes_for_label_property(person_id, name_id);
1901        assert_eq!(indexes.len(), 3);
1902        assert!(indexes.contains(&hash_idx));
1903        assert!(indexes.contains(&btree_idx));
1904        assert!(indexes.contains(&fulltext_idx));
1905
1906        // Verify each has the correct type
1907        assert_eq!(
1908            catalog.get_index(hash_idx).unwrap().index_type,
1909            IndexType::Hash
1910        );
1911        assert_eq!(
1912            catalog.get_index(btree_idx).unwrap().index_type,
1913            IndexType::BTree
1914        );
1915        assert_eq!(
1916            catalog.get_index(fulltext_idx).unwrap().index_type,
1917            IndexType::FullText
1918        );
1919    }
1920
1921    #[test]
1922    fn test_catalog_schema_required_property_duplicate() {
1923        let catalog = Catalog::with_schema();
1924
1925        let person_id = catalog.get_or_create_label("Person");
1926        let name_id = catalog.get_or_create_property_key("name");
1927
1928        // First should succeed
1929        assert!(catalog.add_required_property(person_id, name_id).is_ok());
1930
1931        // Duplicate should fail
1932        assert_eq!(
1933            catalog.add_required_property(person_id, name_id),
1934            Err(CatalogError::ConstraintAlreadyExists)
1935        );
1936    }
1937
1938    #[test]
1939    fn test_catalog_schema_check_without_constraints() {
1940        let catalog = Catalog::new();
1941
1942        let person_id = catalog.get_or_create_label("Person");
1943        let name_id = catalog.get_or_create_property_key("name");
1944
1945        // Without schema enabled, these should return false
1946        assert!(!catalog.is_property_unique(person_id, name_id));
1947        assert!(!catalog.is_property_required(person_id, name_id));
1948    }
1949
1950    #[test]
1951    fn test_catalog_has_schema() {
1952        // Both new() and with_schema() enable schema by default
1953        let catalog = Catalog::new();
1954        assert!(catalog.has_schema());
1955
1956        let with_schema = Catalog::with_schema();
1957        assert!(with_schema.has_schema());
1958    }
1959
1960    #[test]
1961    fn test_catalog_error_display() {
1962        assert_eq!(
1963            CatalogError::SchemaNotEnabled.to_string(),
1964            "Schema constraints are not enabled"
1965        );
1966        assert_eq!(
1967            CatalogError::ConstraintAlreadyExists.to_string(),
1968            "Constraint already exists"
1969        );
1970        assert_eq!(
1971            CatalogError::LabelNotFound("Person".to_string()).to_string(),
1972            "Label not found: Person"
1973        );
1974        assert_eq!(
1975            CatalogError::PropertyKeyNotFound("name".to_string()).to_string(),
1976            "Property key not found: name"
1977        );
1978        assert_eq!(
1979            CatalogError::EdgeTypeNotFound("KNOWS".to_string()).to_string(),
1980            "Edge type not found: KNOWS"
1981        );
1982        let idx = IndexId::new(42);
1983        assert!(CatalogError::IndexNotFound(idx).to_string().contains("42"));
1984    }
1985
1986    #[test]
1987    fn test_catalog_concurrent_label_creation() {
1988        use std::sync::Arc;
1989
1990        let catalog = Arc::new(Catalog::new());
1991        let mut handles = vec![];
1992
1993        // Spawn multiple threads trying to create the same labels
1994        for i in 0..10 {
1995            let catalog = Arc::clone(&catalog);
1996            handles.push(thread::spawn(move || {
1997                let label_name = format!("Label{}", i % 3); // Only 3 unique labels
1998                catalog.get_or_create_label(&label_name)
1999            }));
2000        }
2001
2002        let mut ids: Vec<LabelId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2003        ids.sort_by_key(|id| id.as_u32());
2004        ids.dedup();
2005
2006        // Should only have 3 unique label IDs
2007        assert_eq!(ids.len(), 3);
2008        assert_eq!(catalog.label_count(), 3);
2009    }
2010
2011    #[test]
2012    fn test_catalog_concurrent_property_key_creation() {
2013        use std::sync::Arc;
2014
2015        let catalog = Arc::new(Catalog::new());
2016        let mut handles = vec![];
2017
2018        for i in 0..10 {
2019            let catalog = Arc::clone(&catalog);
2020            handles.push(thread::spawn(move || {
2021                let key_name = format!("key{}", i % 4);
2022                catalog.get_or_create_property_key(&key_name)
2023            }));
2024        }
2025
2026        let mut ids: Vec<PropertyKeyId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2027        ids.sort_by_key(|id| id.as_u32());
2028        ids.dedup();
2029
2030        assert_eq!(ids.len(), 4);
2031        assert_eq!(catalog.property_key_count(), 4);
2032    }
2033
2034    #[test]
2035    fn test_catalog_concurrent_index_operations() {
2036        use std::sync::Arc;
2037
2038        let catalog = Arc::new(Catalog::new());
2039        let label = catalog.get_or_create_label("Node");
2040
2041        let mut handles = vec![];
2042
2043        // Create indexes concurrently
2044        for i in 0..5 {
2045            let catalog = Arc::clone(&catalog);
2046            handles.push(thread::spawn(move || {
2047                let prop = PropertyKeyId::new(i);
2048                catalog.create_index(label, prop, IndexType::Hash)
2049            }));
2050        }
2051
2052        let ids: Vec<IndexId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2053        assert_eq!(ids.len(), 5);
2054        assert_eq!(catalog.index_count(), 5);
2055    }
2056
2057    #[test]
2058    fn test_catalog_special_characters_in_names() {
2059        let catalog = Catalog::new();
2060
2061        // Test with various special characters
2062        let label1 = catalog.get_or_create_label("Label With Spaces");
2063        let label2 = catalog.get_or_create_label("Label-With-Dashes");
2064        let label3 = catalog.get_or_create_label("Label_With_Underscores");
2065        let label4 = catalog.get_or_create_label("LabelWithUnicode\u{00E9}");
2066
2067        assert_ne!(label1, label2);
2068        assert_ne!(label2, label3);
2069        assert_ne!(label3, label4);
2070
2071        assert_eq!(
2072            catalog.get_label_name(label1).as_deref(),
2073            Some("Label With Spaces")
2074        );
2075        assert_eq!(
2076            catalog.get_label_name(label4).as_deref(),
2077            Some("LabelWithUnicode\u{00E9}")
2078        );
2079    }
2080
2081    #[test]
2082    fn test_catalog_empty_names() {
2083        let catalog = Catalog::new();
2084
2085        // Empty names should be valid (edge case)
2086        let empty_label = catalog.get_or_create_label("");
2087        let empty_prop = catalog.get_or_create_property_key("");
2088        let empty_edge = catalog.get_or_create_edge_type("");
2089
2090        assert_eq!(catalog.get_label_name(empty_label).as_deref(), Some(""));
2091        assert_eq!(
2092            catalog.get_property_key_name(empty_prop).as_deref(),
2093            Some("")
2094        );
2095        assert_eq!(catalog.get_edge_type_name(empty_edge).as_deref(), Some(""));
2096
2097        // Calling again should return same ID
2098        assert_eq!(catalog.get_or_create_label(""), empty_label);
2099    }
2100
2101    #[test]
2102    fn test_catalog_large_number_of_entries() {
2103        let catalog = Catalog::new();
2104
2105        // Create many labels
2106        for i in 0..1000 {
2107            catalog.get_or_create_label(&format!("Label{}", i));
2108        }
2109
2110        assert_eq!(catalog.label_count(), 1000);
2111
2112        // Verify we can retrieve them all
2113        let all = catalog.all_labels();
2114        assert_eq!(all.len(), 1000);
2115
2116        // Verify a specific one
2117        let id = catalog.get_label_id("Label500").unwrap();
2118        assert_eq!(catalog.get_label_name(id).as_deref(), Some("Label500"));
2119    }
2120
2121    #[test]
2122    fn test_index_definition_debug() {
2123        let def = IndexDefinition {
2124            id: IndexId::new(1),
2125            label: LabelId::new(2),
2126            property_key: PropertyKeyId::new(3),
2127            index_type: IndexType::Hash,
2128        };
2129
2130        // Should be able to debug print
2131        let debug_str = format!("{:?}", def);
2132        assert!(debug_str.contains("IndexDefinition"));
2133        assert!(debug_str.contains("Hash"));
2134    }
2135
2136    #[test]
2137    fn test_index_type_equality() {
2138        assert_eq!(IndexType::Hash, IndexType::Hash);
2139        assert_ne!(IndexType::Hash, IndexType::BTree);
2140        assert_ne!(IndexType::BTree, IndexType::FullText);
2141
2142        // Clone
2143        let t = IndexType::Hash;
2144        let t2 = t;
2145        assert_eq!(t, t2);
2146    }
2147
2148    #[test]
2149    fn test_catalog_error_equality() {
2150        assert_eq!(
2151            CatalogError::SchemaNotEnabled,
2152            CatalogError::SchemaNotEnabled
2153        );
2154        assert_eq!(
2155            CatalogError::ConstraintAlreadyExists,
2156            CatalogError::ConstraintAlreadyExists
2157        );
2158        assert_eq!(
2159            CatalogError::LabelNotFound("X".to_string()),
2160            CatalogError::LabelNotFound("X".to_string())
2161        );
2162        assert_ne!(
2163            CatalogError::LabelNotFound("X".to_string()),
2164            CatalogError::LabelNotFound("Y".to_string())
2165        );
2166    }
2167}