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
14mod check_eval;
15
16use std::collections::{HashMap, HashSet};
17use std::sync::Arc;
18use std::sync::atomic::{AtomicU32, Ordering};
19
20use parking_lot::{Mutex, RwLock};
21
22use grafeo_common::collections::{GrafeoConcurrentMap, grafeo_concurrent_map};
23use grafeo_common::types::{EdgeTypeId, IndexId, LabelId, PropertyKeyId, Value};
24
25/// The database's schema dictionary - maps names to compact internal IDs.
26///
27/// You rarely interact with this directly. The query processor uses it to
28/// resolve names like "Person" and "name" to internal IDs.
29pub struct Catalog {
30    /// Label name-to-ID mappings.
31    labels: LabelCatalog,
32    /// Property key name-to-ID mappings.
33    property_keys: PropertyCatalog,
34    /// Edge type name-to-ID mappings.
35    edge_types: EdgeTypeCatalog,
36    /// Index definitions.
37    indexes: IndexCatalog,
38    /// Optional schema constraints.
39    schema: Option<SchemaCatalog>,
40}
41
42impl Catalog {
43    /// Creates a new empty catalog with schema support enabled.
44    #[must_use]
45    pub fn new() -> Self {
46        Self {
47            labels: LabelCatalog::new(),
48            property_keys: PropertyCatalog::new(),
49            edge_types: EdgeTypeCatalog::new(),
50            indexes: IndexCatalog::new(),
51            schema: Some(SchemaCatalog::new()),
52        }
53    }
54
55    /// Creates a new catalog with schema constraints enabled.
56    ///
57    /// This is now equivalent to `new()` since schema is always enabled.
58    #[must_use]
59    pub fn with_schema() -> Self {
60        Self::new()
61    }
62
63    // === Label Operations ===
64
65    /// Gets or creates a label ID for the given label name.
66    pub fn get_or_create_label(&self, name: &str) -> LabelId {
67        self.labels.get_or_create(name)
68    }
69
70    /// Gets the label ID for a label name, if it exists.
71    #[must_use]
72    pub fn get_label_id(&self, name: &str) -> Option<LabelId> {
73        self.labels.get_id(name)
74    }
75
76    /// Gets the label name for a label ID, if it exists.
77    #[must_use]
78    pub fn get_label_name(&self, id: LabelId) -> Option<Arc<str>> {
79        self.labels.get_name(id)
80    }
81
82    /// Returns the number of distinct labels.
83    #[must_use]
84    pub fn label_count(&self) -> usize {
85        self.labels.count()
86    }
87
88    /// Returns all label names.
89    #[must_use]
90    pub fn all_labels(&self) -> Vec<Arc<str>> {
91        self.labels.all_names()
92    }
93
94    // === Property Key Operations ===
95
96    /// Gets or creates a property key ID for the given property key name.
97    pub fn get_or_create_property_key(&self, name: &str) -> PropertyKeyId {
98        self.property_keys.get_or_create(name)
99    }
100
101    /// Gets the property key ID for a property key name, if it exists.
102    #[must_use]
103    pub fn get_property_key_id(&self, name: &str) -> Option<PropertyKeyId> {
104        self.property_keys.get_id(name)
105    }
106
107    /// Gets the property key name for a property key ID, if it exists.
108    #[must_use]
109    pub fn get_property_key_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
110        self.property_keys.get_name(id)
111    }
112
113    /// Returns the number of distinct property keys.
114    #[must_use]
115    pub fn property_key_count(&self) -> usize {
116        self.property_keys.count()
117    }
118
119    /// Returns all property key names.
120    #[must_use]
121    pub fn all_property_keys(&self) -> Vec<Arc<str>> {
122        self.property_keys.all_names()
123    }
124
125    // === Edge Type Operations ===
126
127    /// Gets or creates an edge type ID for the given edge type name.
128    pub fn get_or_create_edge_type(&self, name: &str) -> EdgeTypeId {
129        self.edge_types.get_or_create(name)
130    }
131
132    /// Gets the edge type ID for an edge type name, if it exists.
133    #[must_use]
134    pub fn get_edge_type_id(&self, name: &str) -> Option<EdgeTypeId> {
135        self.edge_types.get_id(name)
136    }
137
138    /// Gets the edge type name for an edge type ID, if it exists.
139    #[must_use]
140    pub fn get_edge_type_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
141        self.edge_types.get_name(id)
142    }
143
144    /// Returns the number of distinct edge types.
145    #[must_use]
146    pub fn edge_type_count(&self) -> usize {
147        self.edge_types.count()
148    }
149
150    /// Returns all edge type names.
151    #[must_use]
152    pub fn all_edge_types(&self) -> Vec<Arc<str>> {
153        self.edge_types.all_names()
154    }
155
156    // === Index Operations ===
157
158    /// Creates a new index on a label and property key.
159    pub fn create_index(
160        &self,
161        name: &str,
162        label: LabelId,
163        property_key: PropertyKeyId,
164        index_type: IndexType,
165    ) -> IndexId {
166        self.indexes.create(name, label, property_key, index_type)
167    }
168
169    /// Drops an index by ID.
170    pub fn drop_index(&self, id: IndexId) -> bool {
171        self.indexes.drop(id)
172    }
173
174    /// Finds an index by its user-defined name.
175    #[must_use]
176    pub fn find_index_by_name(&self, name: &str) -> Option<IndexId> {
177        self.indexes.find_by_name(name)
178    }
179
180    /// Gets the index definition for an index ID.
181    #[must_use]
182    pub fn get_index(&self, id: IndexId) -> Option<IndexDefinition> {
183        self.indexes.get(id)
184    }
185
186    /// Finds indexes for a given label.
187    #[must_use]
188    pub fn indexes_for_label(&self, label: LabelId) -> Vec<IndexId> {
189        self.indexes.for_label(label)
190    }
191
192    /// Finds indexes for a given label and property key.
193    #[must_use]
194    pub fn indexes_for_label_property(
195        &self,
196        label: LabelId,
197        property_key: PropertyKeyId,
198    ) -> Vec<IndexId> {
199        self.indexes.for_label_property(label, property_key)
200    }
201
202    /// Returns all index definitions.
203    #[must_use]
204    pub fn all_indexes(&self) -> Vec<IndexDefinition> {
205        self.indexes.all()
206    }
207
208    /// Returns the number of indexes.
209    #[must_use]
210    pub fn index_count(&self) -> usize {
211        self.indexes.count()
212    }
213
214    // === Schema Operations ===
215
216    /// Returns whether schema constraints are enabled.
217    #[must_use]
218    pub fn has_schema(&self) -> bool {
219        self.schema.is_some()
220    }
221
222    /// Adds a uniqueness constraint.
223    ///
224    /// Returns an error if schema is not enabled or constraint already exists.
225    ///
226    /// # Errors
227    ///
228    /// Returns `CatalogError::SchemaNotEnabled` if schema is disabled, or a
229    /// schema-specific error if the operation fails (e.g. duplicate constraint).
230    pub fn add_unique_constraint(
231        &self,
232        label: LabelId,
233        property_key: PropertyKeyId,
234    ) -> Result<(), CatalogError> {
235        match &self.schema {
236            Some(schema) => schema.add_unique_constraint(label, property_key),
237            None => Err(CatalogError::SchemaNotEnabled),
238        }
239    }
240
241    /// Adds a required property constraint (NOT NULL).
242    ///
243    /// Returns an error if schema is not enabled or constraint already exists.
244    ///
245    /// # Errors
246    ///
247    /// Returns `CatalogError::SchemaNotEnabled` if schema is disabled, or a
248    /// schema-specific error if the operation fails (e.g. duplicate constraint).
249    pub fn add_required_property(
250        &self,
251        label: LabelId,
252        property_key: PropertyKeyId,
253    ) -> Result<(), CatalogError> {
254        match &self.schema {
255            Some(schema) => schema.add_required_property(label, property_key),
256            None => Err(CatalogError::SchemaNotEnabled),
257        }
258    }
259
260    /// Checks if a property is required for a label.
261    #[must_use]
262    pub fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
263        self.schema
264            .as_ref()
265            .is_some_and(|s| s.is_property_required(label, property_key))
266    }
267
268    /// Checks if a property must be unique for a label.
269    #[must_use]
270    pub fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
271        self.schema
272            .as_ref()
273            .is_some_and(|s| s.is_property_unique(label, property_key))
274    }
275
276    // === Type Definition Operations ===
277
278    /// Returns a reference to the schema catalog.
279    #[must_use]
280    pub fn schema(&self) -> Option<&SchemaCatalog> {
281        self.schema.as_ref()
282    }
283
284    /// Registers a node type definition.
285    ///
286    /// # Errors
287    ///
288    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
289    /// * `CatalogError::TypeAlreadyExists` if a type with the same name exists.
290    pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
291        match &self.schema {
292            Some(schema) => schema.register_node_type(def),
293            None => Err(CatalogError::SchemaNotEnabled),
294        }
295    }
296
297    /// Registers or replaces a node type definition.
298    pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
299        if let Some(schema) = &self.schema {
300            schema.register_or_replace_node_type(def);
301        }
302    }
303
304    /// Drops a node type definition.
305    ///
306    /// # Errors
307    ///
308    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
309    /// * `CatalogError::TypeNotFound` if the type does not exist.
310    pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
311        match &self.schema {
312            Some(schema) => schema.drop_node_type(name),
313            None => Err(CatalogError::SchemaNotEnabled),
314        }
315    }
316
317    /// Gets a node type definition by name.
318    #[must_use]
319    pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
320        self.schema.as_ref().and_then(|s| s.get_node_type(name))
321    }
322
323    /// Gets a resolved node type with inherited properties from parents.
324    #[must_use]
325    pub fn resolved_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
326        self.schema
327            .as_ref()
328            .and_then(|s| s.resolved_node_type(name))
329    }
330
331    /// Returns all registered node type names.
332    #[must_use]
333    pub fn all_node_type_names(&self) -> Vec<String> {
334        self.schema
335            .as_ref()
336            .map(SchemaCatalog::all_node_types)
337            .unwrap_or_default()
338    }
339
340    /// Returns all registered edge type definition names.
341    #[must_use]
342    pub fn all_edge_type_names(&self) -> Vec<String> {
343        self.schema
344            .as_ref()
345            .map(SchemaCatalog::all_edge_types)
346            .unwrap_or_default()
347    }
348
349    /// Registers an edge type definition.
350    ///
351    /// # Errors
352    ///
353    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
354    /// * `CatalogError::TypeAlreadyExists` if an edge type with the same name exists.
355    pub fn register_edge_type_def(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
356        match &self.schema {
357            Some(schema) => schema.register_edge_type(def),
358            None => Err(CatalogError::SchemaNotEnabled),
359        }
360    }
361
362    /// Registers or replaces an edge type definition.
363    pub fn register_or_replace_edge_type_def(&self, def: EdgeTypeDefinition) {
364        if let Some(schema) = &self.schema {
365            schema.register_or_replace_edge_type(def);
366        }
367    }
368
369    /// Drops an edge type definition.
370    ///
371    /// # Errors
372    ///
373    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
374    /// * `CatalogError::TypeNotFound` if the edge type does not exist.
375    pub fn drop_edge_type_def(&self, name: &str) -> Result<(), CatalogError> {
376        match &self.schema {
377            Some(schema) => schema.drop_edge_type(name),
378            None => Err(CatalogError::SchemaNotEnabled),
379        }
380    }
381
382    /// Gets an edge type definition by name.
383    #[must_use]
384    pub fn get_edge_type_def(&self, name: &str) -> Option<EdgeTypeDefinition> {
385        self.schema.as_ref().and_then(|s| s.get_edge_type(name))
386    }
387
388    /// Registers a graph type definition.
389    ///
390    /// # Errors
391    ///
392    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
393    /// * `CatalogError::TypeAlreadyExists` if a graph type with the same name exists.
394    pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
395        match &self.schema {
396            Some(schema) => schema.register_graph_type(def),
397            None => Err(CatalogError::SchemaNotEnabled),
398        }
399    }
400
401    /// Drops a graph type definition.
402    ///
403    /// # Errors
404    ///
405    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
406    /// * `CatalogError::TypeNotFound` if the graph type does not exist.
407    pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
408        match &self.schema {
409            Some(schema) => schema.drop_graph_type(name),
410            None => Err(CatalogError::SchemaNotEnabled),
411        }
412    }
413
414    /// Returns all registered graph type names.
415    #[must_use]
416    pub fn all_graph_type_names(&self) -> Vec<String> {
417        self.schema
418            .as_ref()
419            .map(SchemaCatalog::all_graph_types)
420            .unwrap_or_default()
421    }
422
423    /// Gets a graph type definition by name.
424    #[must_use]
425    pub fn get_graph_type_def(&self, name: &str) -> Option<GraphTypeDefinition> {
426        self.schema.as_ref().and_then(|s| s.get_graph_type(name))
427    }
428
429    /// Registers a schema namespace.
430    ///
431    /// # Errors
432    ///
433    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
434    /// * `CatalogError::SchemaAlreadyExists` if the namespace already exists.
435    pub fn register_schema_namespace(&self, name: String) -> Result<(), CatalogError> {
436        match &self.schema {
437            Some(schema) => schema.register_schema(name),
438            None => Err(CatalogError::SchemaNotEnabled),
439        }
440    }
441
442    /// Drops a schema namespace.
443    ///
444    /// # Errors
445    ///
446    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
447    /// * `CatalogError::SchemaNotFound` if the namespace does not exist.
448    pub fn drop_schema_namespace(&self, name: &str) -> Result<(), CatalogError> {
449        match &self.schema {
450            Some(schema) => schema.drop_schema(name),
451            None => Err(CatalogError::SchemaNotEnabled),
452        }
453    }
454
455    /// Checks whether a schema namespace exists.
456    #[must_use]
457    pub fn schema_exists(&self, name: &str) -> bool {
458        self.schema.as_ref().is_some_and(|s| s.schema_exists(name))
459    }
460
461    /// Returns all registered schema namespace names.
462    #[must_use]
463    pub fn schema_names(&self) -> Vec<String> {
464        self.schema
465            .as_ref()
466            .map(|s| s.schema_names())
467            .unwrap_or_default()
468    }
469
470    /// Adds a constraint to an existing node type, creating a minimal type if needed.
471    ///
472    /// # Errors
473    ///
474    /// Returns `CatalogError::SchemaNotEnabled` if schema is disabled.
475    pub fn add_constraint_to_type(
476        &self,
477        label: &str,
478        constraint: TypeConstraint,
479    ) -> Result<(), CatalogError> {
480        match &self.schema {
481            Some(schema) => schema.add_constraint_to_type(label, constraint),
482            None => Err(CatalogError::SchemaNotEnabled),
483        }
484    }
485
486    /// Adds a property to a node type.
487    ///
488    /// # Errors
489    ///
490    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
491    /// * `CatalogError::TypeNotFound` if the node type does not exist.
492    /// * `CatalogError::TypeAlreadyExists` if the property already exists on the type.
493    pub fn alter_node_type_add_property(
494        &self,
495        type_name: &str,
496        property: TypedProperty,
497    ) -> Result<(), CatalogError> {
498        match &self.schema {
499            Some(schema) => schema.alter_node_type_add_property(type_name, property),
500            None => Err(CatalogError::SchemaNotEnabled),
501        }
502    }
503
504    /// Drops a property from a node type.
505    ///
506    /// # Errors
507    ///
508    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
509    /// * `CatalogError::TypeNotFound` if the node type or property does not exist.
510    pub fn alter_node_type_drop_property(
511        &self,
512        type_name: &str,
513        property_name: &str,
514    ) -> Result<(), CatalogError> {
515        match &self.schema {
516            Some(schema) => schema.alter_node_type_drop_property(type_name, property_name),
517            None => Err(CatalogError::SchemaNotEnabled),
518        }
519    }
520
521    /// Adds a property to an edge type.
522    ///
523    /// # Errors
524    ///
525    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
526    /// * `CatalogError::TypeNotFound` if the edge type does not exist.
527    /// * `CatalogError::TypeAlreadyExists` if the property already exists on the type.
528    pub fn alter_edge_type_add_property(
529        &self,
530        type_name: &str,
531        property: TypedProperty,
532    ) -> Result<(), CatalogError> {
533        match &self.schema {
534            Some(schema) => schema.alter_edge_type_add_property(type_name, property),
535            None => Err(CatalogError::SchemaNotEnabled),
536        }
537    }
538
539    /// Drops a property from an edge type.
540    ///
541    /// # Errors
542    ///
543    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
544    /// * `CatalogError::TypeNotFound` if the edge type or property does not exist.
545    pub fn alter_edge_type_drop_property(
546        &self,
547        type_name: &str,
548        property_name: &str,
549    ) -> Result<(), CatalogError> {
550        match &self.schema {
551            Some(schema) => schema.alter_edge_type_drop_property(type_name, property_name),
552            None => Err(CatalogError::SchemaNotEnabled),
553        }
554    }
555
556    /// Adds a node type to a graph type.
557    ///
558    /// # Errors
559    ///
560    /// Returns `CatalogError::SchemaNotEnabled` if schema is disabled, or
561    /// `CatalogError::TypeNotFound` if the graph type does not exist.
562    pub fn alter_graph_type_add_node_type(
563        &self,
564        graph_type_name: &str,
565        node_type: String,
566    ) -> Result<(), CatalogError> {
567        match &self.schema {
568            Some(schema) => schema.alter_graph_type_add_node_type(graph_type_name, node_type),
569            None => Err(CatalogError::SchemaNotEnabled),
570        }
571    }
572
573    /// Drops a node type from a graph type.
574    ///
575    /// # Errors
576    ///
577    /// Returns `CatalogError::SchemaNotEnabled` if schema is disabled, or
578    /// `CatalogError::TypeNotFound` if the graph type does not exist.
579    pub fn alter_graph_type_drop_node_type(
580        &self,
581        graph_type_name: &str,
582        node_type: &str,
583    ) -> Result<(), CatalogError> {
584        match &self.schema {
585            Some(schema) => schema.alter_graph_type_drop_node_type(graph_type_name, node_type),
586            None => Err(CatalogError::SchemaNotEnabled),
587        }
588    }
589
590    /// Adds an edge type to a graph type.
591    ///
592    /// # Errors
593    ///
594    /// Returns `CatalogError::SchemaNotEnabled` if schema is disabled, or
595    /// `CatalogError::TypeNotFound` if the graph type does not exist.
596    pub fn alter_graph_type_add_edge_type(
597        &self,
598        graph_type_name: &str,
599        edge_type: String,
600    ) -> Result<(), CatalogError> {
601        match &self.schema {
602            Some(schema) => schema.alter_graph_type_add_edge_type(graph_type_name, edge_type),
603            None => Err(CatalogError::SchemaNotEnabled),
604        }
605    }
606
607    /// Drops an edge type from a graph type.
608    ///
609    /// # Errors
610    ///
611    /// Returns `CatalogError::SchemaNotEnabled` if schema is disabled, or
612    /// `CatalogError::TypeNotFound` if the graph type does not exist.
613    pub fn alter_graph_type_drop_edge_type(
614        &self,
615        graph_type_name: &str,
616        edge_type: &str,
617    ) -> Result<(), CatalogError> {
618        match &self.schema {
619            Some(schema) => schema.alter_graph_type_drop_edge_type(graph_type_name, edge_type),
620            None => Err(CatalogError::SchemaNotEnabled),
621        }
622    }
623
624    /// Binds a graph instance to a graph type.
625    ///
626    /// # Errors
627    ///
628    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
629    /// * `CatalogError::TypeNotFound` if the graph type does not exist.
630    pub fn bind_graph_type(
631        &self,
632        graph_name: &str,
633        graph_type: String,
634    ) -> Result<(), CatalogError> {
635        match &self.schema {
636            Some(schema) => {
637                // Verify the graph type exists
638                if schema.get_graph_type(&graph_type).is_none() {
639                    return Err(CatalogError::TypeNotFound(graph_type));
640                }
641                schema
642                    .graph_type_bindings
643                    .write()
644                    .insert(graph_name.to_string(), graph_type);
645                Ok(())
646            }
647            None => Err(CatalogError::SchemaNotEnabled),
648        }
649    }
650
651    /// Gets the graph type binding for a graph instance.
652    pub fn get_graph_type_binding(&self, graph_name: &str) -> Option<String> {
653        self.schema
654            .as_ref()?
655            .graph_type_bindings
656            .read()
657            .get(graph_name)
658            .cloned()
659    }
660
661    /// Registers a stored procedure.
662    ///
663    /// # Errors
664    ///
665    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
666    /// * `CatalogError::TypeAlreadyExists` if a procedure with the same name exists.
667    pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
668        match &self.schema {
669            Some(schema) => schema.register_procedure(def),
670            None => Err(CatalogError::SchemaNotEnabled),
671        }
672    }
673
674    /// Replaces or creates a stored procedure.
675    ///
676    /// # Errors
677    ///
678    /// Returns `CatalogError::SchemaNotEnabled` if schema is disabled.
679    pub fn replace_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
680        match &self.schema {
681            Some(schema) => {
682                schema.replace_procedure(def);
683                Ok(())
684            }
685            None => Err(CatalogError::SchemaNotEnabled),
686        }
687    }
688
689    /// Drops a stored procedure.
690    ///
691    /// # Errors
692    ///
693    /// * `CatalogError::SchemaNotEnabled` if schema is disabled.
694    /// * `CatalogError::TypeNotFound` if the procedure does not exist.
695    pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
696        match &self.schema {
697            Some(schema) => schema.drop_procedure(name),
698            None => Err(CatalogError::SchemaNotEnabled),
699        }
700    }
701
702    /// Gets a stored procedure by name.
703    pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
704        self.schema.as_ref()?.get_procedure(name)
705    }
706
707    /// Returns all registered node type definitions.
708    #[must_use]
709    pub fn all_node_type_defs(&self) -> Vec<NodeTypeDefinition> {
710        self.schema
711            .as_ref()
712            .map(SchemaCatalog::all_node_type_defs)
713            .unwrap_or_default()
714    }
715
716    /// Returns all registered edge type definitions.
717    #[must_use]
718    pub fn all_edge_type_defs(&self) -> Vec<EdgeTypeDefinition> {
719        self.schema
720            .as_ref()
721            .map(SchemaCatalog::all_edge_type_defs)
722            .unwrap_or_default()
723    }
724
725    /// Returns all registered graph type definitions.
726    #[must_use]
727    pub fn all_graph_type_defs(&self) -> Vec<GraphTypeDefinition> {
728        self.schema
729            .as_ref()
730            .map(SchemaCatalog::all_graph_type_defs)
731            .unwrap_or_default()
732    }
733
734    /// Returns all registered procedure definitions.
735    #[must_use]
736    pub fn all_procedure_defs(&self) -> Vec<ProcedureDefinition> {
737        self.schema
738            .as_ref()
739            .map(SchemaCatalog::all_procedure_defs)
740            .unwrap_or_default()
741    }
742
743    /// Returns all graph type bindings (graph_name, type_name).
744    #[must_use]
745    pub fn all_graph_type_bindings(&self) -> Vec<(String, String)> {
746        self.schema
747            .as_ref()
748            .map(SchemaCatalog::all_graph_type_bindings)
749            .unwrap_or_default()
750    }
751}
752
753impl Default for Catalog {
754    fn default() -> Self {
755        Self::new()
756    }
757}
758
759// === Label Catalog ===
760
761/// Bidirectional mapping between label names and IDs.
762///
763/// Uses `DashMap` (shard-level locking) for `name_to_id` so concurrent
764/// readers never block each other. A separate `Mutex` serializes the rare
765/// create path to keep `id_to_name` consistent.
766struct LabelCatalog {
767    name_to_id: GrafeoConcurrentMap<Arc<str>, LabelId>,
768    id_to_name: RwLock<Vec<Arc<str>>>,
769    next_id: AtomicU32,
770    create_lock: Mutex<()>,
771}
772
773impl LabelCatalog {
774    fn new() -> Self {
775        Self {
776            name_to_id: grafeo_concurrent_map(),
777            id_to_name: RwLock::new(Vec::new()),
778            next_id: AtomicU32::new(0),
779            create_lock: Mutex::new(()),
780        }
781    }
782
783    fn get_or_create(&self, name: &str) -> LabelId {
784        // Fast path: shard-level read (no global lock)
785        if let Some(id) = self.name_to_id.get(name) {
786            return *id;
787        }
788
789        // Slow path: serialize creates to keep id_to_name consistent
790        let _guard = self.create_lock.lock();
791        if let Some(id) = self.name_to_id.get(name) {
792            return *id;
793        }
794
795        let id = LabelId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
796        let name: Arc<str> = name.into();
797        self.id_to_name.write().push(Arc::clone(&name));
798        self.name_to_id.insert(name, id);
799        id
800    }
801
802    fn get_id(&self, name: &str) -> Option<LabelId> {
803        self.name_to_id.get(name).map(|r| *r)
804    }
805
806    fn get_name(&self, id: LabelId) -> Option<Arc<str>> {
807        self.id_to_name.read().get(id.as_u32() as usize).cloned()
808    }
809
810    fn count(&self) -> usize {
811        self.id_to_name.read().len()
812    }
813
814    fn all_names(&self) -> Vec<Arc<str>> {
815        self.id_to_name.read().clone()
816    }
817}
818
819// === Property Catalog ===
820
821/// Bidirectional mapping between property key names and IDs.
822struct PropertyCatalog {
823    name_to_id: GrafeoConcurrentMap<Arc<str>, PropertyKeyId>,
824    id_to_name: RwLock<Vec<Arc<str>>>,
825    next_id: AtomicU32,
826    create_lock: Mutex<()>,
827}
828
829impl PropertyCatalog {
830    fn new() -> Self {
831        Self {
832            name_to_id: grafeo_concurrent_map(),
833            id_to_name: RwLock::new(Vec::new()),
834            next_id: AtomicU32::new(0),
835            create_lock: Mutex::new(()),
836        }
837    }
838
839    fn get_or_create(&self, name: &str) -> PropertyKeyId {
840        // Fast path: shard-level read (no global lock)
841        if let Some(id) = self.name_to_id.get(name) {
842            return *id;
843        }
844
845        // Slow path: serialize creates to keep id_to_name consistent
846        let _guard = self.create_lock.lock();
847        if let Some(id) = self.name_to_id.get(name) {
848            return *id;
849        }
850
851        let id = PropertyKeyId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
852        let name: Arc<str> = name.into();
853        self.id_to_name.write().push(Arc::clone(&name));
854        self.name_to_id.insert(name, id);
855        id
856    }
857
858    fn get_id(&self, name: &str) -> Option<PropertyKeyId> {
859        self.name_to_id.get(name).map(|r| *r)
860    }
861
862    fn get_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
863        self.id_to_name.read().get(id.as_u32() as usize).cloned()
864    }
865
866    fn count(&self) -> usize {
867        self.id_to_name.read().len()
868    }
869
870    fn all_names(&self) -> Vec<Arc<str>> {
871        self.id_to_name.read().clone()
872    }
873}
874
875// === Edge Type Catalog ===
876
877/// Bidirectional mapping between edge type names and IDs.
878struct EdgeTypeCatalog {
879    name_to_id: GrafeoConcurrentMap<Arc<str>, EdgeTypeId>,
880    id_to_name: RwLock<Vec<Arc<str>>>,
881    next_id: AtomicU32,
882    create_lock: Mutex<()>,
883}
884
885impl EdgeTypeCatalog {
886    fn new() -> Self {
887        Self {
888            name_to_id: grafeo_concurrent_map(),
889            id_to_name: RwLock::new(Vec::new()),
890            next_id: AtomicU32::new(0),
891            create_lock: Mutex::new(()),
892        }
893    }
894
895    fn get_or_create(&self, name: &str) -> EdgeTypeId {
896        // Fast path: shard-level read (no global lock)
897        if let Some(id) = self.name_to_id.get(name) {
898            return *id;
899        }
900
901        // Slow path: serialize creates to keep id_to_name consistent
902        let _guard = self.create_lock.lock();
903        if let Some(id) = self.name_to_id.get(name) {
904            return *id;
905        }
906
907        let id = EdgeTypeId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
908        let name: Arc<str> = name.into();
909        self.id_to_name.write().push(Arc::clone(&name));
910        self.name_to_id.insert(name, id);
911        id
912    }
913
914    fn get_id(&self, name: &str) -> Option<EdgeTypeId> {
915        self.name_to_id.get(name).map(|r| *r)
916    }
917
918    fn get_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
919        self.id_to_name.read().get(id.as_u32() as usize).cloned()
920    }
921
922    fn count(&self) -> usize {
923        self.id_to_name.read().len()
924    }
925
926    fn all_names(&self) -> Vec<Arc<str>> {
927        self.id_to_name.read().clone()
928    }
929}
930
931// === Index Catalog ===
932
933/// Type of index.
934#[derive(Debug, Clone, Copy, PartialEq, Eq)]
935#[non_exhaustive]
936pub enum IndexType {
937    /// Hash index for equality lookups.
938    Hash,
939    /// BTree index for range queries.
940    BTree,
941    /// Full-text index for text search.
942    FullText,
943}
944
945/// Index definition.
946#[derive(Debug, Clone)]
947pub struct IndexDefinition {
948    /// The index ID.
949    pub id: IndexId,
950    /// User-defined index name (e.g., `idx_person_name`).
951    pub name: String,
952    /// The label this index applies to.
953    pub label: LabelId,
954    /// The property key being indexed.
955    pub property_key: PropertyKeyId,
956    /// The type of index.
957    pub index_type: IndexType,
958}
959
960/// Manages index definitions.
961struct IndexCatalog {
962    indexes: RwLock<HashMap<IndexId, IndexDefinition>>,
963    label_indexes: RwLock<HashMap<LabelId, Vec<IndexId>>>,
964    label_property_indexes: RwLock<HashMap<(LabelId, PropertyKeyId), Vec<IndexId>>>,
965    name_index: RwLock<HashMap<String, IndexId>>,
966    next_id: AtomicU32,
967}
968
969impl IndexCatalog {
970    fn new() -> Self {
971        Self {
972            indexes: RwLock::new(HashMap::new()),
973            label_indexes: RwLock::new(HashMap::new()),
974            label_property_indexes: RwLock::new(HashMap::new()),
975            name_index: RwLock::new(HashMap::new()),
976            next_id: AtomicU32::new(0),
977        }
978    }
979
980    fn create(
981        &self,
982        name: &str,
983        label: LabelId,
984        property_key: PropertyKeyId,
985        index_type: IndexType,
986    ) -> IndexId {
987        let id = IndexId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
988        let definition = IndexDefinition {
989            id,
990            name: name.to_string(),
991            label,
992            property_key,
993            index_type,
994        };
995
996        let mut indexes = self.indexes.write();
997        let mut label_indexes = self.label_indexes.write();
998        let mut label_property_indexes = self.label_property_indexes.write();
999
1000        indexes.insert(id, definition);
1001        label_indexes.entry(label).or_default().push(id);
1002        label_property_indexes
1003            .entry((label, property_key))
1004            .or_default()
1005            .push(id);
1006        self.name_index.write().insert(name.to_string(), id);
1007
1008        id
1009    }
1010
1011    fn drop(&self, id: IndexId) -> bool {
1012        let mut indexes = self.indexes.write();
1013        let mut label_indexes = self.label_indexes.write();
1014        let mut label_property_indexes = self.label_property_indexes.write();
1015
1016        if let Some(definition) = indexes.remove(&id) {
1017            // Remove from label index
1018            if let Some(ids) = label_indexes.get_mut(&definition.label) {
1019                ids.retain(|&i| i != id);
1020            }
1021            // Remove from label-property index
1022            if let Some(ids) =
1023                label_property_indexes.get_mut(&(definition.label, definition.property_key))
1024            {
1025                ids.retain(|&i| i != id);
1026            }
1027            // Remove from name index
1028            self.name_index.write().remove(&definition.name);
1029            true
1030        } else {
1031            false
1032        }
1033    }
1034
1035    fn find_by_name(&self, name: &str) -> Option<IndexId> {
1036        self.name_index.read().get(name).copied()
1037    }
1038
1039    fn get(&self, id: IndexId) -> Option<IndexDefinition> {
1040        self.indexes.read().get(&id).cloned()
1041    }
1042
1043    fn for_label(&self, label: LabelId) -> Vec<IndexId> {
1044        self.label_indexes
1045            .read()
1046            .get(&label)
1047            .cloned()
1048            .unwrap_or_default()
1049    }
1050
1051    fn for_label_property(&self, label: LabelId, property_key: PropertyKeyId) -> Vec<IndexId> {
1052        self.label_property_indexes
1053            .read()
1054            .get(&(label, property_key))
1055            .cloned()
1056            .unwrap_or_default()
1057    }
1058
1059    fn count(&self) -> usize {
1060        self.indexes.read().len()
1061    }
1062
1063    fn all(&self) -> Vec<IndexDefinition> {
1064        self.indexes.read().values().cloned().collect()
1065    }
1066}
1067
1068// === Type Definitions ===
1069
1070/// Data type for a typed property in a node or edge type definition.
1071#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1072#[non_exhaustive]
1073pub enum PropertyDataType {
1074    /// UTF-8 string.
1075    String,
1076    /// 64-bit signed integer.
1077    Int64,
1078    /// 64-bit floating point.
1079    Float64,
1080    /// Boolean.
1081    Bool,
1082    /// Calendar date.
1083    Date,
1084    /// Time of day.
1085    Time,
1086    /// Timestamp (date + time).
1087    Timestamp,
1088    /// Duration / interval.
1089    Duration,
1090    /// Ordered list of values (untyped).
1091    List,
1092    /// Typed list: `LIST<element_type>` (ISO sec 4.16.9).
1093    ListTyped(Box<PropertyDataType>),
1094    /// Key-value map.
1095    Map,
1096    /// Raw bytes.
1097    Bytes,
1098    /// Node reference type (ISO sec 4.15.1).
1099    Node,
1100    /// Edge reference type (ISO sec 4.15.1).
1101    Edge,
1102    /// Any type (no enforcement).
1103    Any,
1104}
1105
1106impl PropertyDataType {
1107    /// Parses a type name string (case-insensitive) into a `PropertyDataType`.
1108    #[must_use]
1109    pub fn from_type_name(name: &str) -> Self {
1110        let upper = name.to_uppercase();
1111        // Handle parameterized LIST<element_type>
1112        if let Some(inner) = upper
1113            .strip_prefix("LIST<")
1114            .and_then(|s| s.strip_suffix('>'))
1115        {
1116            return Self::ListTyped(Box::new(Self::from_type_name(inner)));
1117        }
1118        match upper.as_str() {
1119            "STRING" | "VARCHAR" | "TEXT" => Self::String,
1120            "INT" | "INT64" | "INTEGER" | "BIGINT" => Self::Int64,
1121            "FLOAT" | "FLOAT64" | "DOUBLE" | "REAL" => Self::Float64,
1122            "BOOL" | "BOOLEAN" => Self::Bool,
1123            "DATE" => Self::Date,
1124            "TIME" => Self::Time,
1125            "TIMESTAMP" | "DATETIME" => Self::Timestamp,
1126            "DURATION" | "INTERVAL" => Self::Duration,
1127            "LIST" | "ARRAY" => Self::List,
1128            "MAP" | "RECORD" => Self::Map,
1129            "BYTES" | "BINARY" | "BLOB" => Self::Bytes,
1130            "NODE" => Self::Node,
1131            "EDGE" | "RELATIONSHIP" => Self::Edge,
1132            _ => Self::Any,
1133        }
1134    }
1135
1136    /// Checks whether a value conforms to this type.
1137    #[must_use]
1138    pub fn matches(&self, value: &Value) -> bool {
1139        match (self, value) {
1140            (Self::Any, _) | (_, Value::Null) => true,
1141            (Self::String, Value::String(_)) => true,
1142            (Self::Int64, Value::Int64(_)) => true,
1143            (Self::Float64, Value::Float64(_)) => true,
1144            (Self::Bool, Value::Bool(_)) => true,
1145            (Self::Date, Value::Date(_)) => true,
1146            (Self::Time, Value::Time(_)) => true,
1147            (Self::Timestamp, Value::Timestamp(_)) => true,
1148            (Self::Duration, Value::Duration(_)) => true,
1149            (Self::List, Value::List(_)) => true,
1150            (Self::ListTyped(elem_type), Value::List(items)) => {
1151                items.iter().all(|item| elem_type.matches(item))
1152            }
1153            (Self::Bytes, Value::Bytes(_)) => true,
1154            // Node/Edge reference types match Map values (graph elements are
1155            // represented as maps with _id, _labels/_type, and properties)
1156            (Self::Node | Self::Edge, Value::Map(_)) => true,
1157            _ => false,
1158        }
1159    }
1160}
1161
1162impl std::fmt::Display for PropertyDataType {
1163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1164        match self {
1165            Self::String => write!(f, "STRING"),
1166            Self::Int64 => write!(f, "INT64"),
1167            Self::Float64 => write!(f, "FLOAT64"),
1168            Self::Bool => write!(f, "BOOLEAN"),
1169            Self::Date => write!(f, "DATE"),
1170            Self::Time => write!(f, "TIME"),
1171            Self::Timestamp => write!(f, "TIMESTAMP"),
1172            Self::Duration => write!(f, "DURATION"),
1173            Self::List => write!(f, "LIST"),
1174            Self::ListTyped(elem) => write!(f, "LIST<{elem}>"),
1175            Self::Map => write!(f, "MAP"),
1176            Self::Bytes => write!(f, "BYTES"),
1177            Self::Node => write!(f, "NODE"),
1178            Self::Edge => write!(f, "EDGE"),
1179            Self::Any => write!(f, "ANY"),
1180        }
1181    }
1182}
1183
1184/// A typed property within a node or edge type definition.
1185#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1186pub struct TypedProperty {
1187    /// Property name.
1188    pub name: String,
1189    /// Expected data type.
1190    pub data_type: PropertyDataType,
1191    /// Whether NULL values are allowed.
1192    pub nullable: bool,
1193    /// Default value (used when property is not explicitly set).
1194    pub default_value: Option<Value>,
1195}
1196
1197/// A constraint on a node or edge type.
1198#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1199#[non_exhaustive]
1200pub enum TypeConstraint {
1201    /// Primary key (implies UNIQUE + NOT NULL).
1202    PrimaryKey(Vec<String>),
1203    /// Uniqueness constraint on one or more properties.
1204    Unique(Vec<String>),
1205    /// NOT NULL constraint on a single property.
1206    NotNull(String),
1207    /// CHECK constraint with a named expression string.
1208    Check {
1209        /// Optional constraint name.
1210        name: Option<String>,
1211        /// Expression (stored as string for now).
1212        expression: String,
1213    },
1214}
1215
1216/// Definition of a node type (label schema).
1217#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1218pub struct NodeTypeDefinition {
1219    /// Type name (corresponds to a label).
1220    pub name: String,
1221    /// Typed property definitions.
1222    pub properties: Vec<TypedProperty>,
1223    /// Type-level constraints.
1224    pub constraints: Vec<TypeConstraint>,
1225    /// Parent type names for inheritance (GQL `EXTENDS`).
1226    pub parent_types: Vec<String>,
1227}
1228
1229/// Definition of an edge type (relationship type schema).
1230#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1231pub struct EdgeTypeDefinition {
1232    /// Type name (corresponds to an edge type / relationship type).
1233    pub name: String,
1234    /// Typed property definitions.
1235    pub properties: Vec<TypedProperty>,
1236    /// Type-level constraints.
1237    pub constraints: Vec<TypeConstraint>,
1238    /// Allowed source node types (empty = any).
1239    pub source_node_types: Vec<String>,
1240    /// Allowed target node types (empty = any).
1241    pub target_node_types: Vec<String>,
1242}
1243
1244/// Definition of a graph type (constrains which node/edge types a graph allows).
1245#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1246pub struct GraphTypeDefinition {
1247    /// Graph type name.
1248    pub name: String,
1249    /// Allowed node types (empty = open).
1250    pub allowed_node_types: Vec<String>,
1251    /// Allowed edge types (empty = open).
1252    pub allowed_edge_types: Vec<String>,
1253    /// Whether unlisted types are permitted.
1254    pub open: bool,
1255}
1256
1257/// Definition of a stored procedure.
1258#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1259pub struct ProcedureDefinition {
1260    /// Procedure name.
1261    pub name: String,
1262    /// Parameter definitions: (name, type).
1263    pub params: Vec<(String, String)>,
1264    /// Return column definitions: (name, type).
1265    pub returns: Vec<(String, String)>,
1266    /// Raw GQL query body.
1267    pub body: String,
1268}
1269
1270// === Schema Catalog ===
1271
1272/// Schema constraints and type definitions.
1273pub struct SchemaCatalog {
1274    /// Properties that must be unique for a given label.
1275    unique_constraints: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1276    /// Properties that are required (NOT NULL) for a given label.
1277    required_properties: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1278    /// Registered node type definitions.
1279    node_types: RwLock<HashMap<String, NodeTypeDefinition>>,
1280    /// Registered edge type definitions.
1281    edge_types: RwLock<HashMap<String, EdgeTypeDefinition>>,
1282    /// Registered graph type definitions.
1283    graph_types: RwLock<HashMap<String, GraphTypeDefinition>>,
1284    /// Schema namespaces.
1285    schemas: RwLock<Vec<String>>,
1286    /// Graph instance to graph type bindings.
1287    graph_type_bindings: RwLock<HashMap<String, String>>,
1288    /// Stored procedure definitions.
1289    procedures: RwLock<HashMap<String, ProcedureDefinition>>,
1290}
1291
1292impl SchemaCatalog {
1293    fn new() -> Self {
1294        Self {
1295            unique_constraints: RwLock::new(HashSet::new()),
1296            required_properties: RwLock::new(HashSet::new()),
1297            node_types: RwLock::new(HashMap::new()),
1298            edge_types: RwLock::new(HashMap::new()),
1299            graph_types: RwLock::new(HashMap::new()),
1300            schemas: RwLock::new(Vec::new()),
1301            graph_type_bindings: RwLock::new(HashMap::new()),
1302            procedures: RwLock::new(HashMap::new()),
1303        }
1304    }
1305
1306    // --- Node type operations ---
1307
1308    /// Registers a new node type definition.
1309    ///
1310    /// # Errors
1311    ///
1312    /// Returns `CatalogError::TypeAlreadyExists` if a type with the same name exists.
1313    pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
1314        let mut types = self.node_types.write();
1315        if types.contains_key(&def.name) {
1316            return Err(CatalogError::TypeAlreadyExists(def.name));
1317        }
1318        types.insert(def.name.clone(), def);
1319        Ok(())
1320    }
1321
1322    /// Registers or replaces a node type definition.
1323    pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
1324        self.node_types.write().insert(def.name.clone(), def);
1325    }
1326
1327    /// Drops a node type definition by name.
1328    ///
1329    /// # Errors
1330    ///
1331    /// Returns `CatalogError::TypeNotFound` if no type with the given name exists.
1332    pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
1333        let mut types = self.node_types.write();
1334        if types.remove(name).is_none() {
1335            return Err(CatalogError::TypeNotFound(name.to_string()));
1336        }
1337        Ok(())
1338    }
1339
1340    /// Gets a node type definition by name.
1341    #[must_use]
1342    pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1343        self.node_types.read().get(name).cloned()
1344    }
1345
1346    /// Gets a resolved node type with inherited properties and constraints from parents.
1347    ///
1348    /// Walks the parent chain depth-first, collecting properties and constraints.
1349    /// Detects cycles via a visited set. Child properties override parent ones
1350    /// with the same name.
1351    #[must_use]
1352    pub fn resolved_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1353        let types = self.node_types.read();
1354        let base = types.get(name)?;
1355        if base.parent_types.is_empty() {
1356            return Some(base.clone());
1357        }
1358        let mut visited = HashSet::new();
1359        visited.insert(name.to_string());
1360        let mut all_properties = Vec::new();
1361        let mut all_constraints = Vec::new();
1362        Self::collect_inherited(
1363            &types,
1364            name,
1365            &mut visited,
1366            &mut all_properties,
1367            &mut all_constraints,
1368        );
1369        Some(NodeTypeDefinition {
1370            name: base.name.clone(),
1371            properties: all_properties,
1372            constraints: all_constraints,
1373            parent_types: base.parent_types.clone(),
1374        })
1375    }
1376
1377    /// Recursively collects properties and constraints from a type and its parents.
1378    fn collect_inherited(
1379        types: &HashMap<String, NodeTypeDefinition>,
1380        name: &str,
1381        visited: &mut HashSet<String>,
1382        properties: &mut Vec<TypedProperty>,
1383        constraints: &mut Vec<TypeConstraint>,
1384    ) {
1385        let Some(def) = types.get(name) else { return };
1386        // Walk parents first (depth-first) so child properties override
1387        for parent in &def.parent_types {
1388            if visited.insert(parent.clone()) {
1389                Self::collect_inherited(types, parent, visited, properties, constraints);
1390            }
1391        }
1392        // Add own properties, overriding parent ones with same name
1393        for prop in &def.properties {
1394            if let Some(pos) = properties.iter().position(|p| p.name == prop.name) {
1395                properties[pos] = prop.clone();
1396            } else {
1397                properties.push(prop.clone());
1398            }
1399        }
1400        // Append own constraints (no dedup, constraints are additive)
1401        constraints.extend(def.constraints.iter().cloned());
1402    }
1403
1404    /// Returns all registered node type names.
1405    #[must_use]
1406    pub fn all_node_types(&self) -> Vec<String> {
1407        self.node_types.read().keys().cloned().collect()
1408    }
1409
1410    /// Returns all registered node type definitions.
1411    #[must_use]
1412    pub fn all_node_type_defs(&self) -> Vec<NodeTypeDefinition> {
1413        self.node_types.read().values().cloned().collect()
1414    }
1415
1416    // --- Edge type operations ---
1417
1418    /// Registers a new edge type definition.
1419    ///
1420    /// # Errors
1421    ///
1422    /// Returns `CatalogError::TypeAlreadyExists` if an edge type with the same name exists.
1423    pub fn register_edge_type(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
1424        let mut types = self.edge_types.write();
1425        if types.contains_key(&def.name) {
1426            return Err(CatalogError::TypeAlreadyExists(def.name));
1427        }
1428        types.insert(def.name.clone(), def);
1429        Ok(())
1430    }
1431
1432    /// Registers or replaces an edge type definition.
1433    pub fn register_or_replace_edge_type(&self, def: EdgeTypeDefinition) {
1434        self.edge_types.write().insert(def.name.clone(), def);
1435    }
1436
1437    /// Drops an edge type definition by name.
1438    ///
1439    /// # Errors
1440    ///
1441    /// Returns `CatalogError::TypeNotFound` if no edge type with the given name exists.
1442    pub fn drop_edge_type(&self, name: &str) -> Result<(), CatalogError> {
1443        let mut types = self.edge_types.write();
1444        if types.remove(name).is_none() {
1445            return Err(CatalogError::TypeNotFound(name.to_string()));
1446        }
1447        Ok(())
1448    }
1449
1450    /// Gets an edge type definition by name.
1451    #[must_use]
1452    pub fn get_edge_type(&self, name: &str) -> Option<EdgeTypeDefinition> {
1453        self.edge_types.read().get(name).cloned()
1454    }
1455
1456    /// Returns all registered edge type names.
1457    #[must_use]
1458    pub fn all_edge_types(&self) -> Vec<String> {
1459        self.edge_types.read().keys().cloned().collect()
1460    }
1461
1462    /// Returns all registered edge type definitions.
1463    #[must_use]
1464    pub fn all_edge_type_defs(&self) -> Vec<EdgeTypeDefinition> {
1465        self.edge_types.read().values().cloned().collect()
1466    }
1467
1468    // --- Graph type operations ---
1469
1470    /// Registers a new graph type definition.
1471    ///
1472    /// # Errors
1473    ///
1474    /// Returns `CatalogError::TypeAlreadyExists` if a graph type with the same name exists.
1475    pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
1476        let mut types = self.graph_types.write();
1477        if types.contains_key(&def.name) {
1478            return Err(CatalogError::TypeAlreadyExists(def.name));
1479        }
1480        types.insert(def.name.clone(), def);
1481        Ok(())
1482    }
1483
1484    /// Drops a graph type definition by name.
1485    ///
1486    /// # Errors
1487    ///
1488    /// Returns `CatalogError::TypeNotFound` if no graph type with the given name exists.
1489    pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
1490        let mut types = self.graph_types.write();
1491        if types.remove(name).is_none() {
1492            return Err(CatalogError::TypeNotFound(name.to_string()));
1493        }
1494        Ok(())
1495    }
1496
1497    /// Gets a graph type definition by name.
1498    #[must_use]
1499    pub fn get_graph_type(&self, name: &str) -> Option<GraphTypeDefinition> {
1500        self.graph_types.read().get(name).cloned()
1501    }
1502
1503    /// Returns all registered graph type names.
1504    #[must_use]
1505    pub fn all_graph_types(&self) -> Vec<String> {
1506        self.graph_types.read().keys().cloned().collect()
1507    }
1508
1509    /// Returns all registered graph type definitions.
1510    #[must_use]
1511    pub fn all_graph_type_defs(&self) -> Vec<GraphTypeDefinition> {
1512        self.graph_types.read().values().cloned().collect()
1513    }
1514
1515    // --- Schema namespace operations ---
1516
1517    /// Registers a schema namespace.
1518    ///
1519    /// # Errors
1520    ///
1521    /// Returns `CatalogError::SchemaAlreadyExists` if the namespace already exists.
1522    pub fn register_schema(&self, name: String) -> Result<(), CatalogError> {
1523        let mut schemas = self.schemas.write();
1524        if schemas.contains(&name) {
1525            return Err(CatalogError::SchemaAlreadyExists(name));
1526        }
1527        schemas.push(name);
1528        Ok(())
1529    }
1530
1531    /// Drops a schema namespace.
1532    ///
1533    /// # Errors
1534    ///
1535    /// Returns `CatalogError::SchemaNotFound` if the namespace does not exist.
1536    pub fn drop_schema(&self, name: &str) -> Result<(), CatalogError> {
1537        let mut schemas = self.schemas.write();
1538        if let Some(pos) = schemas.iter().position(|s| s == name) {
1539            schemas.remove(pos);
1540            Ok(())
1541        } else {
1542            Err(CatalogError::SchemaNotFound(name.to_string()))
1543        }
1544    }
1545
1546    /// Checks whether a schema namespace exists.
1547    #[must_use]
1548    pub fn schema_exists(&self, name: &str) -> bool {
1549        self.schemas
1550            .read()
1551            .iter()
1552            .any(|s| s.eq_ignore_ascii_case(name))
1553    }
1554
1555    /// Returns all registered schema namespace names.
1556    #[must_use]
1557    pub fn schema_names(&self) -> Vec<String> {
1558        self.schemas.read().clone()
1559    }
1560
1561    // --- ALTER operations ---
1562
1563    /// Adds a constraint to an existing node type, creating a minimal type if needed.
1564    ///
1565    /// # Errors
1566    ///
1567    /// Currently infallible, but returns `Result` for forward compatibility.
1568    pub fn add_constraint_to_type(
1569        &self,
1570        label: &str,
1571        constraint: TypeConstraint,
1572    ) -> Result<(), CatalogError> {
1573        let mut types = self.node_types.write();
1574        if let Some(def) = types.get_mut(label) {
1575            def.constraints.push(constraint);
1576        } else {
1577            // Auto-create a minimal type definition for the label
1578            types.insert(
1579                label.to_string(),
1580                NodeTypeDefinition {
1581                    name: label.to_string(),
1582                    properties: Vec::new(),
1583                    constraints: vec![constraint],
1584                    parent_types: Vec::new(),
1585                },
1586            );
1587        }
1588        Ok(())
1589    }
1590
1591    /// Adds a property to an existing node type.
1592    ///
1593    /// # Errors
1594    ///
1595    /// * `CatalogError::TypeNotFound` if the node type does not exist.
1596    /// * `CatalogError::TypeAlreadyExists` if the property already exists on the type.
1597    pub fn alter_node_type_add_property(
1598        &self,
1599        type_name: &str,
1600        property: TypedProperty,
1601    ) -> Result<(), CatalogError> {
1602        let mut types = self.node_types.write();
1603        let def = types
1604            .get_mut(type_name)
1605            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1606        if def.properties.iter().any(|p| p.name == property.name) {
1607            return Err(CatalogError::TypeAlreadyExists(format!(
1608                "property {} on {}",
1609                property.name, type_name
1610            )));
1611        }
1612        def.properties.push(property);
1613        Ok(())
1614    }
1615
1616    /// Drops a property from an existing node type.
1617    ///
1618    /// # Errors
1619    ///
1620    /// Returns `CatalogError::TypeNotFound` if the node type or property does not exist.
1621    pub fn alter_node_type_drop_property(
1622        &self,
1623        type_name: &str,
1624        property_name: &str,
1625    ) -> Result<(), CatalogError> {
1626        let mut types = self.node_types.write();
1627        let def = types
1628            .get_mut(type_name)
1629            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1630        let len_before = def.properties.len();
1631        def.properties.retain(|p| p.name != property_name);
1632        if def.properties.len() == len_before {
1633            return Err(CatalogError::TypeNotFound(format!(
1634                "property {} on {}",
1635                property_name, type_name
1636            )));
1637        }
1638        Ok(())
1639    }
1640
1641    /// Adds a property to an existing edge type.
1642    ///
1643    /// # Errors
1644    ///
1645    /// * `CatalogError::TypeNotFound` if the edge type does not exist.
1646    /// * `CatalogError::TypeAlreadyExists` if the property already exists on the type.
1647    pub fn alter_edge_type_add_property(
1648        &self,
1649        type_name: &str,
1650        property: TypedProperty,
1651    ) -> Result<(), CatalogError> {
1652        let mut types = self.edge_types.write();
1653        let def = types
1654            .get_mut(type_name)
1655            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1656        if def.properties.iter().any(|p| p.name == property.name) {
1657            return Err(CatalogError::TypeAlreadyExists(format!(
1658                "property {} on {}",
1659                property.name, type_name
1660            )));
1661        }
1662        def.properties.push(property);
1663        Ok(())
1664    }
1665
1666    /// Drops a property from an existing edge type.
1667    ///
1668    /// # Errors
1669    ///
1670    /// Returns `CatalogError::TypeNotFound` if the edge type or property does not exist.
1671    pub fn alter_edge_type_drop_property(
1672        &self,
1673        type_name: &str,
1674        property_name: &str,
1675    ) -> Result<(), CatalogError> {
1676        let mut types = self.edge_types.write();
1677        let def = types
1678            .get_mut(type_name)
1679            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1680        let len_before = def.properties.len();
1681        def.properties.retain(|p| p.name != property_name);
1682        if def.properties.len() == len_before {
1683            return Err(CatalogError::TypeNotFound(format!(
1684                "property {} on {}",
1685                property_name, type_name
1686            )));
1687        }
1688        Ok(())
1689    }
1690
1691    /// Adds a node type to a graph type.
1692    ///
1693    /// # Errors
1694    ///
1695    /// Returns `CatalogError::TypeNotFound` if the graph type does not exist.
1696    pub fn alter_graph_type_add_node_type(
1697        &self,
1698        graph_type_name: &str,
1699        node_type: String,
1700    ) -> Result<(), CatalogError> {
1701        let mut types = self.graph_types.write();
1702        let def = types
1703            .get_mut(graph_type_name)
1704            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1705        if !def.allowed_node_types.contains(&node_type) {
1706            def.allowed_node_types.push(node_type);
1707        }
1708        Ok(())
1709    }
1710
1711    /// Drops a node type from a graph type.
1712    ///
1713    /// # Errors
1714    ///
1715    /// Returns `CatalogError::TypeNotFound` if the graph type does not exist.
1716    pub fn alter_graph_type_drop_node_type(
1717        &self,
1718        graph_type_name: &str,
1719        node_type: &str,
1720    ) -> Result<(), CatalogError> {
1721        let mut types = self.graph_types.write();
1722        let def = types
1723            .get_mut(graph_type_name)
1724            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1725        def.allowed_node_types.retain(|t| t != node_type);
1726        Ok(())
1727    }
1728
1729    /// Adds an edge type to a graph type.
1730    ///
1731    /// # Errors
1732    ///
1733    /// Returns `CatalogError::TypeNotFound` if the graph type does not exist.
1734    pub fn alter_graph_type_add_edge_type(
1735        &self,
1736        graph_type_name: &str,
1737        edge_type: String,
1738    ) -> Result<(), CatalogError> {
1739        let mut types = self.graph_types.write();
1740        let def = types
1741            .get_mut(graph_type_name)
1742            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1743        if !def.allowed_edge_types.contains(&edge_type) {
1744            def.allowed_edge_types.push(edge_type);
1745        }
1746        Ok(())
1747    }
1748
1749    /// Drops an edge type from a graph type.
1750    ///
1751    /// # Errors
1752    ///
1753    /// Returns `CatalogError::TypeNotFound` if the graph type does not exist.
1754    pub fn alter_graph_type_drop_edge_type(
1755        &self,
1756        graph_type_name: &str,
1757        edge_type: &str,
1758    ) -> Result<(), CatalogError> {
1759        let mut types = self.graph_types.write();
1760        let def = types
1761            .get_mut(graph_type_name)
1762            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1763        def.allowed_edge_types.retain(|t| t != edge_type);
1764        Ok(())
1765    }
1766
1767    // --- Procedure operations ---
1768
1769    /// Registers a stored procedure.
1770    ///
1771    /// # Errors
1772    ///
1773    /// Returns `CatalogError::TypeAlreadyExists` if a procedure with the same name exists.
1774    pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
1775        let mut procs = self.procedures.write();
1776        if procs.contains_key(&def.name) {
1777            return Err(CatalogError::TypeAlreadyExists(def.name.clone()));
1778        }
1779        procs.insert(def.name.clone(), def);
1780        Ok(())
1781    }
1782
1783    /// Replaces or creates a stored procedure.
1784    pub fn replace_procedure(&self, def: ProcedureDefinition) {
1785        self.procedures.write().insert(def.name.clone(), def);
1786    }
1787
1788    /// Drops a stored procedure.
1789    ///
1790    /// # Errors
1791    ///
1792    /// Returns `CatalogError::TypeNotFound` if no procedure with the given name exists.
1793    pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
1794        let mut procs = self.procedures.write();
1795        if procs.remove(name).is_none() {
1796            return Err(CatalogError::TypeNotFound(name.to_string()));
1797        }
1798        Ok(())
1799    }
1800
1801    /// Gets a stored procedure by name.
1802    pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
1803        self.procedures.read().get(name).cloned()
1804    }
1805
1806    /// Returns all registered procedure definitions.
1807    #[must_use]
1808    pub fn all_procedure_defs(&self) -> Vec<ProcedureDefinition> {
1809        self.procedures.read().values().cloned().collect()
1810    }
1811
1812    /// Returns all graph type bindings (graph_name, type_name).
1813    #[must_use]
1814    pub fn all_graph_type_bindings(&self) -> Vec<(String, String)> {
1815        self.graph_type_bindings
1816            .read()
1817            .iter()
1818            .map(|(k, v)| (k.clone(), v.clone()))
1819            .collect()
1820    }
1821
1822    fn add_unique_constraint(
1823        &self,
1824        label: LabelId,
1825        property_key: PropertyKeyId,
1826    ) -> Result<(), CatalogError> {
1827        let mut constraints = self.unique_constraints.write();
1828        let key = (label, property_key);
1829        if !constraints.insert(key) {
1830            return Err(CatalogError::ConstraintAlreadyExists);
1831        }
1832        Ok(())
1833    }
1834
1835    fn add_required_property(
1836        &self,
1837        label: LabelId,
1838        property_key: PropertyKeyId,
1839    ) -> Result<(), CatalogError> {
1840        let mut required = self.required_properties.write();
1841        let key = (label, property_key);
1842        if !required.insert(key) {
1843            return Err(CatalogError::ConstraintAlreadyExists);
1844        }
1845        Ok(())
1846    }
1847
1848    fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1849        self.required_properties
1850            .read()
1851            .contains(&(label, property_key))
1852    }
1853
1854    fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1855        self.unique_constraints
1856            .read()
1857            .contains(&(label, property_key))
1858    }
1859}
1860
1861// === Errors ===
1862
1863/// Catalog-related errors.
1864#[derive(Debug, Clone, PartialEq, Eq)]
1865#[non_exhaustive]
1866pub enum CatalogError {
1867    /// Schema constraints are not enabled.
1868    SchemaNotEnabled,
1869    /// The constraint already exists.
1870    ConstraintAlreadyExists,
1871    /// The label does not exist.
1872    LabelNotFound(String),
1873    /// The property key does not exist.
1874    PropertyKeyNotFound(String),
1875    /// The edge type does not exist.
1876    EdgeTypeNotFound(String),
1877    /// The index does not exist.
1878    IndexNotFound(IndexId),
1879    /// A type with this name already exists.
1880    TypeAlreadyExists(String),
1881    /// No type with this name exists.
1882    TypeNotFound(String),
1883    /// A schema with this name already exists.
1884    SchemaAlreadyExists(String),
1885    /// No schema with this name exists.
1886    SchemaNotFound(String),
1887}
1888
1889impl std::fmt::Display for CatalogError {
1890    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1891        match self {
1892            Self::SchemaNotEnabled => write!(f, "Schema constraints are not enabled"),
1893            Self::ConstraintAlreadyExists => write!(f, "Constraint already exists"),
1894            Self::LabelNotFound(name) => write!(f, "Label not found: {name}"),
1895            Self::PropertyKeyNotFound(name) => write!(f, "Property key not found: {name}"),
1896            Self::EdgeTypeNotFound(name) => write!(f, "Edge type not found: {name}"),
1897            Self::IndexNotFound(id) => write!(f, "Index not found: {id}"),
1898            Self::TypeAlreadyExists(name) => write!(f, "Type already exists: {name}"),
1899            Self::TypeNotFound(name) => write!(f, "Type not found: {name}"),
1900            Self::SchemaAlreadyExists(name) => write!(f, "Schema already exists: {name}"),
1901            Self::SchemaNotFound(name) => write!(f, "Schema not found: {name}"),
1902        }
1903    }
1904}
1905
1906impl std::error::Error for CatalogError {}
1907
1908// === Constraint Validator ===
1909
1910use grafeo_core::execution::operators::ConstraintValidator;
1911use grafeo_core::execution::operators::OperatorError;
1912
1913/// Validates schema constraints during mutation operations using the Catalog.
1914///
1915/// Checks type definitions, NOT NULL constraints, and UNIQUE constraints
1916/// against registered node/edge type definitions.
1917pub struct CatalogConstraintValidator {
1918    catalog: Arc<Catalog>,
1919    /// Optional graph name for graph-type-bound validation.
1920    graph_name: Option<String>,
1921    /// Optional graph store for UNIQUE constraint enforcement via index lookup.
1922    store: Option<Arc<dyn grafeo_core::graph::GraphStore>>,
1923}
1924
1925impl CatalogConstraintValidator {
1926    /// Creates a new validator wrapping the given catalog.
1927    pub fn new(catalog: Arc<Catalog>) -> Self {
1928        Self {
1929            catalog,
1930            graph_name: None,
1931            store: None,
1932        }
1933    }
1934
1935    /// Sets the graph name for graph-type-bound validation.
1936    pub fn with_graph_name(mut self, name: String) -> Self {
1937        self.graph_name = Some(name);
1938        self
1939    }
1940
1941    /// Attaches a graph store for UNIQUE constraint enforcement.
1942    pub fn with_store(mut self, store: Arc<dyn grafeo_core::graph::GraphStore>) -> Self {
1943        self.store = Some(store);
1944        self
1945    }
1946}
1947
1948impl ConstraintValidator for CatalogConstraintValidator {
1949    fn validate_node_property(
1950        &self,
1951        labels: &[String],
1952        key: &str,
1953        value: &Value,
1954    ) -> Result<(), OperatorError> {
1955        for label in labels {
1956            if let Some(type_def) = self.catalog.resolved_node_type(label)
1957                && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1958            {
1959                // Check NOT NULL
1960                if !typed_prop.nullable && *value == Value::Null {
1961                    return Err(OperatorError::ConstraintViolation(format!(
1962                        "property '{key}' on :{label} is NOT NULL, cannot set to null"
1963                    )));
1964                }
1965                // Check type compatibility
1966                if *value != Value::Null && !typed_prop.data_type.matches(value) {
1967                    return Err(OperatorError::ConstraintViolation(format!(
1968                        "property '{key}' on :{label} expects {:?}, got {:?}",
1969                        typed_prop.data_type, value
1970                    )));
1971                }
1972            }
1973        }
1974        Ok(())
1975    }
1976
1977    fn validate_node_complete(
1978        &self,
1979        labels: &[String],
1980        properties: &[(String, Value)],
1981    ) -> Result<(), OperatorError> {
1982        let prop_names: std::collections::HashSet<&str> =
1983            properties.iter().map(|(n, _)| n.as_str()).collect();
1984
1985        for label in labels {
1986            if let Some(type_def) = self.catalog.resolved_node_type(label) {
1987                // Check that all NOT NULL properties are present
1988                for typed_prop in &type_def.properties {
1989                    if !typed_prop.nullable
1990                        && typed_prop.default_value.is_none()
1991                        && !prop_names.contains(typed_prop.name.as_str())
1992                    {
1993                        return Err(OperatorError::ConstraintViolation(format!(
1994                            "missing required property '{}' on :{label}",
1995                            typed_prop.name
1996                        )));
1997                    }
1998                }
1999                // Check type-level constraints
2000                for constraint in &type_def.constraints {
2001                    match constraint {
2002                        TypeConstraint::NotNull(prop_name) => {
2003                            if !prop_names.contains(prop_name.as_str()) {
2004                                return Err(OperatorError::ConstraintViolation(format!(
2005                                    "missing required property '{prop_name}' on :{label} (NOT NULL constraint)"
2006                                )));
2007                            }
2008                        }
2009                        TypeConstraint::PrimaryKey(key_props) => {
2010                            for pk in key_props {
2011                                if !prop_names.contains(pk.as_str()) {
2012                                    return Err(OperatorError::ConstraintViolation(format!(
2013                                        "missing primary key property '{pk}' on :{label}"
2014                                    )));
2015                                }
2016                            }
2017                        }
2018                        TypeConstraint::Check { name, expression } => {
2019                            match check_eval::evaluate_check(expression, properties) {
2020                                Ok(true) => {}
2021                                Ok(false) => {
2022                                    let constraint_name = name.as_deref().unwrap_or("unnamed");
2023                                    return Err(OperatorError::ConstraintViolation(format!(
2024                                        "CHECK constraint '{constraint_name}' violated on :{label}"
2025                                    )));
2026                                }
2027                                Err(err) => {
2028                                    return Err(OperatorError::ConstraintViolation(format!(
2029                                        "CHECK constraint evaluation error: {err}"
2030                                    )));
2031                                }
2032                            }
2033                        }
2034                        TypeConstraint::Unique(_) => {}
2035                    }
2036                }
2037            }
2038        }
2039        Ok(())
2040    }
2041
2042    fn check_unique_node_property(
2043        &self,
2044        labels: &[String],
2045        key: &str,
2046        value: &Value,
2047    ) -> Result<(), OperatorError> {
2048        // Skip uniqueness check for NULL values (NULLs are never duplicates)
2049        if *value == Value::Null {
2050            return Ok(());
2051        }
2052        for label in labels {
2053            if let Some(type_def) = self.catalog.resolved_node_type(label) {
2054                for constraint in &type_def.constraints {
2055                    let is_unique = match constraint {
2056                        TypeConstraint::Unique(props) => props.iter().any(|p| p == key),
2057                        TypeConstraint::PrimaryKey(props) => props.iter().any(|p| p == key),
2058                        _ => false,
2059                    };
2060                    if is_unique && let Some(ref store) = self.store {
2061                        let existing = store.find_nodes_by_property(key, value);
2062                        for node_id in existing {
2063                            if let Some(node) = store.get_node(node_id) {
2064                                let has_label = node.labels.iter().any(|l| l.as_str() == label);
2065                                if has_label {
2066                                    return Err(OperatorError::ConstraintViolation(format!(
2067                                        "UNIQUE constraint violation: property '{key}' \
2068                                             with value {value:?} already exists on :{label}"
2069                                    )));
2070                                }
2071                            }
2072                        }
2073                    }
2074                }
2075            }
2076        }
2077        Ok(())
2078    }
2079
2080    fn validate_edge_property(
2081        &self,
2082        edge_type: &str,
2083        key: &str,
2084        value: &Value,
2085    ) -> Result<(), OperatorError> {
2086        if let Some(type_def) = self.catalog.get_edge_type_def(edge_type)
2087            && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
2088        {
2089            // Check NOT NULL
2090            if !typed_prop.nullable && *value == Value::Null {
2091                return Err(OperatorError::ConstraintViolation(format!(
2092                    "property '{key}' on :{edge_type} is NOT NULL, cannot set to null"
2093                )));
2094            }
2095            // Check type compatibility
2096            if *value != Value::Null && !typed_prop.data_type.matches(value) {
2097                return Err(OperatorError::ConstraintViolation(format!(
2098                    "property '{key}' on :{edge_type} expects {:?}, got {:?}",
2099                    typed_prop.data_type, value
2100                )));
2101            }
2102        }
2103        Ok(())
2104    }
2105
2106    fn validate_edge_complete(
2107        &self,
2108        edge_type: &str,
2109        properties: &[(String, Value)],
2110    ) -> Result<(), OperatorError> {
2111        if let Some(type_def) = self.catalog.get_edge_type_def(edge_type) {
2112            let prop_names: std::collections::HashSet<&str> =
2113                properties.iter().map(|(n, _)| n.as_str()).collect();
2114
2115            for typed_prop in &type_def.properties {
2116                if !typed_prop.nullable
2117                    && typed_prop.default_value.is_none()
2118                    && !prop_names.contains(typed_prop.name.as_str())
2119                {
2120                    return Err(OperatorError::ConstraintViolation(format!(
2121                        "missing required property '{}' on :{edge_type}",
2122                        typed_prop.name
2123                    )));
2124                }
2125            }
2126
2127            for constraint in &type_def.constraints {
2128                if let TypeConstraint::Check { name, expression } = constraint {
2129                    match check_eval::evaluate_check(expression, properties) {
2130                        Ok(true) => {}
2131                        Ok(false) => {
2132                            let constraint_name = name.as_deref().unwrap_or("unnamed");
2133                            return Err(OperatorError::ConstraintViolation(format!(
2134                                "CHECK constraint '{constraint_name}' violated on :{edge_type}"
2135                            )));
2136                        }
2137                        Err(err) => {
2138                            return Err(OperatorError::ConstraintViolation(format!(
2139                                "CHECK constraint evaluation error: {err}"
2140                            )));
2141                        }
2142                    }
2143                }
2144            }
2145        }
2146        Ok(())
2147    }
2148
2149    fn validate_node_labels_allowed(&self, labels: &[String]) -> Result<(), OperatorError> {
2150        let Some(ref graph_name) = self.graph_name else {
2151            return Ok(());
2152        };
2153        let Some(type_name) = self.catalog.get_graph_type_binding(graph_name) else {
2154            return Ok(());
2155        };
2156        let Some(gt) = self
2157            .catalog
2158            .schema()
2159            .and_then(|s| s.get_graph_type(&type_name))
2160        else {
2161            return Ok(());
2162        };
2163        if !gt.open && !gt.allowed_node_types.is_empty() {
2164            let allowed = labels
2165                .iter()
2166                .any(|l| gt.allowed_node_types.iter().any(|a| a == l));
2167            if !allowed {
2168                return Err(OperatorError::ConstraintViolation(format!(
2169                    "node labels {labels:?} are not allowed by graph type '{}'",
2170                    gt.name
2171                )));
2172            }
2173        }
2174        Ok(())
2175    }
2176
2177    fn validate_edge_type_allowed(&self, edge_type: &str) -> Result<(), OperatorError> {
2178        let Some(ref graph_name) = self.graph_name else {
2179            return Ok(());
2180        };
2181        let Some(type_name) = self.catalog.get_graph_type_binding(graph_name) else {
2182            return Ok(());
2183        };
2184        let Some(gt) = self
2185            .catalog
2186            .schema()
2187            .and_then(|s| s.get_graph_type(&type_name))
2188        else {
2189            return Ok(());
2190        };
2191        if !gt.open && !gt.allowed_edge_types.is_empty() {
2192            let allowed = gt.allowed_edge_types.iter().any(|a| a == edge_type);
2193            if !allowed {
2194                return Err(OperatorError::ConstraintViolation(format!(
2195                    "edge type '{edge_type}' is not allowed by graph type '{}'",
2196                    gt.name
2197                )));
2198            }
2199        }
2200        Ok(())
2201    }
2202
2203    fn validate_edge_endpoints(
2204        &self,
2205        edge_type: &str,
2206        source_labels: &[String],
2207        target_labels: &[String],
2208    ) -> Result<(), OperatorError> {
2209        let Some(type_def) = self.catalog.get_edge_type_def(edge_type) else {
2210            return Ok(());
2211        };
2212        if !type_def.source_node_types.is_empty() {
2213            let source_ok = source_labels
2214                .iter()
2215                .any(|l| type_def.source_node_types.iter().any(|s| s == l));
2216            if !source_ok {
2217                return Err(OperatorError::ConstraintViolation(format!(
2218                    "source node labels {source_labels:?} are not allowed for edge type '{edge_type}', \
2219                     expected one of {:?}",
2220                    type_def.source_node_types
2221                )));
2222            }
2223        }
2224        if !type_def.target_node_types.is_empty() {
2225            let target_ok = target_labels
2226                .iter()
2227                .any(|l| type_def.target_node_types.iter().any(|t| t == l));
2228            if !target_ok {
2229                return Err(OperatorError::ConstraintViolation(format!(
2230                    "target node labels {target_labels:?} are not allowed for edge type '{edge_type}', \
2231                     expected one of {:?}",
2232                    type_def.target_node_types
2233                )));
2234            }
2235        }
2236        Ok(())
2237    }
2238
2239    fn inject_defaults(&self, labels: &[String], properties: &mut Vec<(String, Value)>) {
2240        for label in labels {
2241            if let Some(type_def) = self.catalog.resolved_node_type(label) {
2242                for typed_prop in &type_def.properties {
2243                    if let Some(ref default) = typed_prop.default_value {
2244                        let already_set = properties.iter().any(|(n, _)| n == &typed_prop.name);
2245                        if !already_set {
2246                            properties.push((typed_prop.name.clone(), default.clone()));
2247                        }
2248                    }
2249                }
2250            }
2251        }
2252    }
2253}
2254
2255#[cfg(test)]
2256mod tests {
2257    use super::*;
2258    use std::thread;
2259
2260    #[test]
2261    fn test_catalog_labels() {
2262        let catalog = Catalog::new();
2263
2264        // Get or create labels
2265        let person_id = catalog.get_or_create_label("Person");
2266        let company_id = catalog.get_or_create_label("Company");
2267
2268        // IDs should be different
2269        assert_ne!(person_id, company_id);
2270
2271        // Getting the same label should return the same ID
2272        assert_eq!(catalog.get_or_create_label("Person"), person_id);
2273
2274        // Should be able to look up by name
2275        assert_eq!(catalog.get_label_id("Person"), Some(person_id));
2276        assert_eq!(catalog.get_label_id("Company"), Some(company_id));
2277        assert_eq!(catalog.get_label_id("Unknown"), None);
2278
2279        // Should be able to look up by ID
2280        assert_eq!(catalog.get_label_name(person_id).as_deref(), Some("Person"));
2281        assert_eq!(
2282            catalog.get_label_name(company_id).as_deref(),
2283            Some("Company")
2284        );
2285
2286        // Count should be correct
2287        assert_eq!(catalog.label_count(), 2);
2288    }
2289
2290    #[test]
2291    fn test_catalog_property_keys() {
2292        let catalog = Catalog::new();
2293
2294        let name_id = catalog.get_or_create_property_key("name");
2295        let age_id = catalog.get_or_create_property_key("age");
2296
2297        assert_ne!(name_id, age_id);
2298        assert_eq!(catalog.get_or_create_property_key("name"), name_id);
2299        assert_eq!(catalog.get_property_key_id("name"), Some(name_id));
2300        assert_eq!(
2301            catalog.get_property_key_name(name_id).as_deref(),
2302            Some("name")
2303        );
2304        assert_eq!(catalog.property_key_count(), 2);
2305    }
2306
2307    #[test]
2308    fn test_catalog_edge_types() {
2309        let catalog = Catalog::new();
2310
2311        let knows_id = catalog.get_or_create_edge_type("KNOWS");
2312        let works_at_id = catalog.get_or_create_edge_type("WORKS_AT");
2313
2314        assert_ne!(knows_id, works_at_id);
2315        assert_eq!(catalog.get_or_create_edge_type("KNOWS"), knows_id);
2316        assert_eq!(catalog.get_edge_type_id("KNOWS"), Some(knows_id));
2317        assert_eq!(
2318            catalog.get_edge_type_name(knows_id).as_deref(),
2319            Some("KNOWS")
2320        );
2321        assert_eq!(catalog.edge_type_count(), 2);
2322    }
2323
2324    #[test]
2325    fn test_catalog_indexes() {
2326        let catalog = Catalog::new();
2327
2328        let person_id = catalog.get_or_create_label("Person");
2329        let name_id = catalog.get_or_create_property_key("name");
2330        let age_id = catalog.get_or_create_property_key("age");
2331
2332        // Create indexes
2333        let idx1 = catalog.create_index("idx_person_name", person_id, name_id, IndexType::Hash);
2334        let idx2 = catalog.create_index("idx_person_age", person_id, age_id, IndexType::BTree);
2335
2336        assert_ne!(idx1, idx2);
2337        assert_eq!(catalog.index_count(), 2);
2338
2339        // Look up by label
2340        let label_indexes = catalog.indexes_for_label(person_id);
2341        assert_eq!(label_indexes.len(), 2);
2342        assert!(label_indexes.contains(&idx1));
2343        assert!(label_indexes.contains(&idx2));
2344
2345        // Look up by label and property
2346        let name_indexes = catalog.indexes_for_label_property(person_id, name_id);
2347        assert_eq!(name_indexes.len(), 1);
2348        assert_eq!(name_indexes[0], idx1);
2349
2350        // Get definition
2351        let def = catalog.get_index(idx1).unwrap();
2352        assert_eq!(def.label, person_id);
2353        assert_eq!(def.property_key, name_id);
2354        assert_eq!(def.index_type, IndexType::Hash);
2355
2356        // Drop index
2357        assert!(catalog.drop_index(idx1));
2358        assert_eq!(catalog.index_count(), 1);
2359        assert!(catalog.get_index(idx1).is_none());
2360        assert_eq!(catalog.indexes_for_label(person_id).len(), 1);
2361    }
2362
2363    #[test]
2364    fn test_catalog_schema_constraints() {
2365        let catalog = Catalog::with_schema();
2366
2367        let person_id = catalog.get_or_create_label("Person");
2368        let email_id = catalog.get_or_create_property_key("email");
2369        let name_id = catalog.get_or_create_property_key("name");
2370
2371        // Add constraints
2372        assert!(catalog.add_unique_constraint(person_id, email_id).is_ok());
2373        assert!(catalog.add_required_property(person_id, name_id).is_ok());
2374
2375        // Check constraints
2376        assert!(catalog.is_property_unique(person_id, email_id));
2377        assert!(!catalog.is_property_unique(person_id, name_id));
2378        assert!(catalog.is_property_required(person_id, name_id));
2379        assert!(!catalog.is_property_required(person_id, email_id));
2380
2381        // Duplicate constraint should fail
2382        assert_eq!(
2383            catalog.add_unique_constraint(person_id, email_id),
2384            Err(CatalogError::ConstraintAlreadyExists)
2385        );
2386    }
2387
2388    #[test]
2389    fn test_catalog_schema_always_enabled() {
2390        // Catalog::new() always enables schema
2391        let catalog = Catalog::new();
2392        assert!(catalog.has_schema());
2393
2394        let person_id = catalog.get_or_create_label("Person");
2395        let email_id = catalog.get_or_create_property_key("email");
2396
2397        // Should succeed with schema enabled
2398        assert_eq!(catalog.add_unique_constraint(person_id, email_id), Ok(()));
2399    }
2400
2401    // === Additional tests for comprehensive coverage ===
2402
2403    #[test]
2404    fn test_catalog_default() {
2405        let catalog = Catalog::default();
2406        assert!(catalog.has_schema());
2407        assert_eq!(catalog.label_count(), 0);
2408        assert_eq!(catalog.property_key_count(), 0);
2409        assert_eq!(catalog.edge_type_count(), 0);
2410        assert_eq!(catalog.index_count(), 0);
2411    }
2412
2413    #[test]
2414    fn test_catalog_all_labels() {
2415        let catalog = Catalog::new();
2416
2417        catalog.get_or_create_label("Person");
2418        catalog.get_or_create_label("Company");
2419        catalog.get_or_create_label("Product");
2420
2421        let all = catalog.all_labels();
2422        assert_eq!(all.len(), 3);
2423        assert!(all.iter().any(|l| l.as_ref() == "Person"));
2424        assert!(all.iter().any(|l| l.as_ref() == "Company"));
2425        assert!(all.iter().any(|l| l.as_ref() == "Product"));
2426    }
2427
2428    #[test]
2429    fn test_catalog_all_property_keys() {
2430        let catalog = Catalog::new();
2431
2432        catalog.get_or_create_property_key("name");
2433        catalog.get_or_create_property_key("age");
2434        catalog.get_or_create_property_key("email");
2435
2436        let all = catalog.all_property_keys();
2437        assert_eq!(all.len(), 3);
2438        assert!(all.iter().any(|k| k.as_ref() == "name"));
2439        assert!(all.iter().any(|k| k.as_ref() == "age"));
2440        assert!(all.iter().any(|k| k.as_ref() == "email"));
2441    }
2442
2443    #[test]
2444    fn test_catalog_all_edge_types() {
2445        let catalog = Catalog::new();
2446
2447        catalog.get_or_create_edge_type("KNOWS");
2448        catalog.get_or_create_edge_type("WORKS_AT");
2449        catalog.get_or_create_edge_type("LIVES_IN");
2450
2451        let all = catalog.all_edge_types();
2452        assert_eq!(all.len(), 3);
2453        assert!(all.iter().any(|t| t.as_ref() == "KNOWS"));
2454        assert!(all.iter().any(|t| t.as_ref() == "WORKS_AT"));
2455        assert!(all.iter().any(|t| t.as_ref() == "LIVES_IN"));
2456    }
2457
2458    #[test]
2459    fn test_catalog_invalid_id_lookup() {
2460        let catalog = Catalog::new();
2461
2462        // Create one label to ensure IDs are allocated
2463        let _ = catalog.get_or_create_label("Person");
2464
2465        // Try to look up non-existent IDs
2466        let invalid_label = LabelId::new(999);
2467        let invalid_property = PropertyKeyId::new(999);
2468        let invalid_edge_type = EdgeTypeId::new(999);
2469        let invalid_index = IndexId::new(999);
2470
2471        assert!(catalog.get_label_name(invalid_label).is_none());
2472        assert!(catalog.get_property_key_name(invalid_property).is_none());
2473        assert!(catalog.get_edge_type_name(invalid_edge_type).is_none());
2474        assert!(catalog.get_index(invalid_index).is_none());
2475    }
2476
2477    #[test]
2478    fn test_catalog_drop_nonexistent_index() {
2479        let catalog = Catalog::new();
2480        let invalid_index = IndexId::new(999);
2481        assert!(!catalog.drop_index(invalid_index));
2482    }
2483
2484    #[test]
2485    fn test_catalog_indexes_for_nonexistent_label() {
2486        let catalog = Catalog::new();
2487        let invalid_label = LabelId::new(999);
2488        let invalid_property = PropertyKeyId::new(999);
2489
2490        assert!(catalog.indexes_for_label(invalid_label).is_empty());
2491        assert!(
2492            catalog
2493                .indexes_for_label_property(invalid_label, invalid_property)
2494                .is_empty()
2495        );
2496    }
2497
2498    #[test]
2499    fn test_catalog_multiple_indexes_same_property() {
2500        let catalog = Catalog::new();
2501
2502        let person_id = catalog.get_or_create_label("Person");
2503        let name_id = catalog.get_or_create_property_key("name");
2504
2505        // Create multiple indexes on the same property with different types
2506        let hash_idx = catalog.create_index("idx_hash", person_id, name_id, IndexType::Hash);
2507        let btree_idx = catalog.create_index("idx_btree", person_id, name_id, IndexType::BTree);
2508        let fulltext_idx =
2509            catalog.create_index("idx_fulltext", person_id, name_id, IndexType::FullText);
2510
2511        assert_eq!(catalog.index_count(), 3);
2512
2513        let indexes = catalog.indexes_for_label_property(person_id, name_id);
2514        assert_eq!(indexes.len(), 3);
2515        assert!(indexes.contains(&hash_idx));
2516        assert!(indexes.contains(&btree_idx));
2517        assert!(indexes.contains(&fulltext_idx));
2518
2519        // Verify each has the correct type
2520        assert_eq!(
2521            catalog.get_index(hash_idx).unwrap().index_type,
2522            IndexType::Hash
2523        );
2524        assert_eq!(
2525            catalog.get_index(btree_idx).unwrap().index_type,
2526            IndexType::BTree
2527        );
2528        assert_eq!(
2529            catalog.get_index(fulltext_idx).unwrap().index_type,
2530            IndexType::FullText
2531        );
2532    }
2533
2534    #[test]
2535    fn test_catalog_schema_required_property_duplicate() {
2536        let catalog = Catalog::with_schema();
2537
2538        let person_id = catalog.get_or_create_label("Person");
2539        let name_id = catalog.get_or_create_property_key("name");
2540
2541        // First should succeed
2542        assert!(catalog.add_required_property(person_id, name_id).is_ok());
2543
2544        // Duplicate should fail
2545        assert_eq!(
2546            catalog.add_required_property(person_id, name_id),
2547            Err(CatalogError::ConstraintAlreadyExists)
2548        );
2549    }
2550
2551    #[test]
2552    fn test_catalog_schema_check_without_constraints() {
2553        let catalog = Catalog::new();
2554
2555        let person_id = catalog.get_or_create_label("Person");
2556        let name_id = catalog.get_or_create_property_key("name");
2557
2558        // Without schema enabled, these should return false
2559        assert!(!catalog.is_property_unique(person_id, name_id));
2560        assert!(!catalog.is_property_required(person_id, name_id));
2561    }
2562
2563    #[test]
2564    fn test_catalog_has_schema() {
2565        // Both new() and with_schema() enable schema by default
2566        let catalog = Catalog::new();
2567        assert!(catalog.has_schema());
2568
2569        let with_schema = Catalog::with_schema();
2570        assert!(with_schema.has_schema());
2571    }
2572
2573    #[test]
2574    fn test_catalog_error_display() {
2575        assert_eq!(
2576            CatalogError::SchemaNotEnabled.to_string(),
2577            "Schema constraints are not enabled"
2578        );
2579        assert_eq!(
2580            CatalogError::ConstraintAlreadyExists.to_string(),
2581            "Constraint already exists"
2582        );
2583        assert_eq!(
2584            CatalogError::LabelNotFound("Person".to_string()).to_string(),
2585            "Label not found: Person"
2586        );
2587        assert_eq!(
2588            CatalogError::PropertyKeyNotFound("name".to_string()).to_string(),
2589            "Property key not found: name"
2590        );
2591        assert_eq!(
2592            CatalogError::EdgeTypeNotFound("KNOWS".to_string()).to_string(),
2593            "Edge type not found: KNOWS"
2594        );
2595        let idx = IndexId::new(42);
2596        assert!(CatalogError::IndexNotFound(idx).to_string().contains("42"));
2597    }
2598
2599    #[test]
2600    fn test_catalog_concurrent_label_creation() {
2601        use std::sync::Arc;
2602
2603        let catalog = Arc::new(Catalog::new());
2604        let mut handles = vec![];
2605
2606        // Spawn multiple threads trying to create the same labels
2607        for i in 0..10 {
2608            let catalog = Arc::clone(&catalog);
2609            handles.push(thread::spawn(move || {
2610                let label_name = format!("Label{}", i % 3); // Only 3 unique labels
2611                catalog.get_or_create_label(&label_name)
2612            }));
2613        }
2614
2615        let mut ids: Vec<LabelId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2616        ids.sort_by_key(|id| id.as_u32());
2617        ids.dedup();
2618
2619        // Should only have 3 unique label IDs
2620        assert_eq!(ids.len(), 3);
2621        assert_eq!(catalog.label_count(), 3);
2622    }
2623
2624    #[test]
2625    fn test_catalog_concurrent_property_key_creation() {
2626        use std::sync::Arc;
2627
2628        let catalog = Arc::new(Catalog::new());
2629        let mut handles = vec![];
2630
2631        for i in 0..10 {
2632            let catalog = Arc::clone(&catalog);
2633            handles.push(thread::spawn(move || {
2634                let key_name = format!("key{}", i % 4);
2635                catalog.get_or_create_property_key(&key_name)
2636            }));
2637        }
2638
2639        let mut ids: Vec<PropertyKeyId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2640        ids.sort_by_key(|id| id.as_u32());
2641        ids.dedup();
2642
2643        assert_eq!(ids.len(), 4);
2644        assert_eq!(catalog.property_key_count(), 4);
2645    }
2646
2647    #[test]
2648    fn test_catalog_concurrent_index_operations() {
2649        use std::sync::Arc;
2650
2651        let catalog = Arc::new(Catalog::new());
2652        let label = catalog.get_or_create_label("Node");
2653
2654        let mut handles = vec![];
2655
2656        // Create indexes concurrently
2657        for i in 0..5 {
2658            let catalog = Arc::clone(&catalog);
2659            handles.push(thread::spawn(move || {
2660                let prop = PropertyKeyId::new(i);
2661                catalog.create_index(&format!("idx_{i}"), label, prop, IndexType::Hash)
2662            }));
2663        }
2664
2665        let ids: Vec<IndexId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2666        assert_eq!(ids.len(), 5);
2667        assert_eq!(catalog.index_count(), 5);
2668    }
2669
2670    #[test]
2671    fn test_catalog_special_characters_in_names() {
2672        let catalog = Catalog::new();
2673
2674        // Test with various special characters
2675        let label1 = catalog.get_or_create_label("Label With Spaces");
2676        let label2 = catalog.get_or_create_label("Label-With-Dashes");
2677        let label3 = catalog.get_or_create_label("Label_With_Underscores");
2678        let label4 = catalog.get_or_create_label("LabelWithUnicode\u{00E9}");
2679
2680        assert_ne!(label1, label2);
2681        assert_ne!(label2, label3);
2682        assert_ne!(label3, label4);
2683
2684        assert_eq!(
2685            catalog.get_label_name(label1).as_deref(),
2686            Some("Label With Spaces")
2687        );
2688        assert_eq!(
2689            catalog.get_label_name(label4).as_deref(),
2690            Some("LabelWithUnicode\u{00E9}")
2691        );
2692    }
2693
2694    #[test]
2695    fn test_catalog_empty_names() {
2696        let catalog = Catalog::new();
2697
2698        // Empty names should be valid (edge case)
2699        let empty_label = catalog.get_or_create_label("");
2700        let empty_prop = catalog.get_or_create_property_key("");
2701        let empty_edge = catalog.get_or_create_edge_type("");
2702
2703        assert_eq!(catalog.get_label_name(empty_label).as_deref(), Some(""));
2704        assert_eq!(
2705            catalog.get_property_key_name(empty_prop).as_deref(),
2706            Some("")
2707        );
2708        assert_eq!(catalog.get_edge_type_name(empty_edge).as_deref(), Some(""));
2709
2710        // Calling again should return same ID
2711        assert_eq!(catalog.get_or_create_label(""), empty_label);
2712    }
2713
2714    #[test]
2715    fn test_catalog_large_number_of_entries() {
2716        let catalog = Catalog::new();
2717
2718        // Create many labels
2719        for i in 0..1000 {
2720            catalog.get_or_create_label(&format!("Label{}", i));
2721        }
2722
2723        assert_eq!(catalog.label_count(), 1000);
2724
2725        // Verify we can retrieve them all
2726        let all = catalog.all_labels();
2727        assert_eq!(all.len(), 1000);
2728
2729        // Verify a specific one
2730        let id = catalog.get_label_id("Label500").unwrap();
2731        assert_eq!(catalog.get_label_name(id).as_deref(), Some("Label500"));
2732    }
2733
2734    #[test]
2735    fn test_index_definition_debug() {
2736        let def = IndexDefinition {
2737            id: IndexId::new(1),
2738            name: "test_index".to_string(),
2739            label: LabelId::new(2),
2740            property_key: PropertyKeyId::new(3),
2741            index_type: IndexType::Hash,
2742        };
2743
2744        // Should be able to debug print
2745        let debug_str = format!("{:?}", def);
2746        assert!(debug_str.contains("IndexDefinition"));
2747        assert!(debug_str.contains("Hash"));
2748    }
2749
2750    #[test]
2751    fn test_index_type_equality() {
2752        assert_eq!(IndexType::Hash, IndexType::Hash);
2753        assert_ne!(IndexType::Hash, IndexType::BTree);
2754        assert_ne!(IndexType::BTree, IndexType::FullText);
2755
2756        // Clone
2757        let t = IndexType::Hash;
2758        let t2 = t;
2759        assert_eq!(t, t2);
2760    }
2761
2762    #[test]
2763    fn test_catalog_error_equality() {
2764        assert_eq!(
2765            CatalogError::SchemaNotEnabled,
2766            CatalogError::SchemaNotEnabled
2767        );
2768        assert_eq!(
2769            CatalogError::ConstraintAlreadyExists,
2770            CatalogError::ConstraintAlreadyExists
2771        );
2772        assert_eq!(
2773            CatalogError::LabelNotFound("X".to_string()),
2774            CatalogError::LabelNotFound("X".to_string())
2775        );
2776        assert_ne!(
2777            CatalogError::LabelNotFound("X".to_string()),
2778            CatalogError::LabelNotFound("Y".to_string())
2779        );
2780    }
2781}