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)]
1072pub enum PropertyDataType {
1073    /// UTF-8 string.
1074    String,
1075    /// 64-bit signed integer.
1076    Int64,
1077    /// 64-bit floating point.
1078    Float64,
1079    /// Boolean.
1080    Bool,
1081    /// Calendar date.
1082    Date,
1083    /// Time of day.
1084    Time,
1085    /// Timestamp (date + time).
1086    Timestamp,
1087    /// Duration / interval.
1088    Duration,
1089    /// Ordered list of values (untyped).
1090    List,
1091    /// Typed list: `LIST<element_type>` (ISO sec 4.16.9).
1092    ListTyped(Box<PropertyDataType>),
1093    /// Key-value map.
1094    Map,
1095    /// Raw bytes.
1096    Bytes,
1097    /// Node reference type (ISO sec 4.15.1).
1098    Node,
1099    /// Edge reference type (ISO sec 4.15.1).
1100    Edge,
1101    /// Any type (no enforcement).
1102    Any,
1103}
1104
1105impl PropertyDataType {
1106    /// Parses a type name string (case-insensitive) into a `PropertyDataType`.
1107    #[must_use]
1108    pub fn from_type_name(name: &str) -> Self {
1109        let upper = name.to_uppercase();
1110        // Handle parameterized LIST<element_type>
1111        if let Some(inner) = upper
1112            .strip_prefix("LIST<")
1113            .and_then(|s| s.strip_suffix('>'))
1114        {
1115            return Self::ListTyped(Box::new(Self::from_type_name(inner)));
1116        }
1117        match upper.as_str() {
1118            "STRING" | "VARCHAR" | "TEXT" => Self::String,
1119            "INT" | "INT64" | "INTEGER" | "BIGINT" => Self::Int64,
1120            "FLOAT" | "FLOAT64" | "DOUBLE" | "REAL" => Self::Float64,
1121            "BOOL" | "BOOLEAN" => Self::Bool,
1122            "DATE" => Self::Date,
1123            "TIME" => Self::Time,
1124            "TIMESTAMP" | "DATETIME" => Self::Timestamp,
1125            "DURATION" | "INTERVAL" => Self::Duration,
1126            "LIST" | "ARRAY" => Self::List,
1127            "MAP" | "RECORD" => Self::Map,
1128            "BYTES" | "BINARY" | "BLOB" => Self::Bytes,
1129            "NODE" => Self::Node,
1130            "EDGE" | "RELATIONSHIP" => Self::Edge,
1131            _ => Self::Any,
1132        }
1133    }
1134
1135    /// Checks whether a value conforms to this type.
1136    #[must_use]
1137    pub fn matches(&self, value: &Value) -> bool {
1138        match (self, value) {
1139            (Self::Any, _) | (_, Value::Null) => true,
1140            (Self::String, Value::String(_)) => true,
1141            (Self::Int64, Value::Int64(_)) => true,
1142            (Self::Float64, Value::Float64(_)) => true,
1143            (Self::Bool, Value::Bool(_)) => true,
1144            (Self::Date, Value::Date(_)) => true,
1145            (Self::Time, Value::Time(_)) => true,
1146            (Self::Timestamp, Value::Timestamp(_)) => true,
1147            (Self::Duration, Value::Duration(_)) => true,
1148            (Self::List, Value::List(_)) => true,
1149            (Self::ListTyped(elem_type), Value::List(items)) => {
1150                items.iter().all(|item| elem_type.matches(item))
1151            }
1152            (Self::Bytes, Value::Bytes(_)) => true,
1153            // Node/Edge reference types match Map values (graph elements are
1154            // represented as maps with _id, _labels/_type, and properties)
1155            (Self::Node | Self::Edge, Value::Map(_)) => true,
1156            _ => false,
1157        }
1158    }
1159}
1160
1161impl std::fmt::Display for PropertyDataType {
1162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1163        match self {
1164            Self::String => write!(f, "STRING"),
1165            Self::Int64 => write!(f, "INT64"),
1166            Self::Float64 => write!(f, "FLOAT64"),
1167            Self::Bool => write!(f, "BOOLEAN"),
1168            Self::Date => write!(f, "DATE"),
1169            Self::Time => write!(f, "TIME"),
1170            Self::Timestamp => write!(f, "TIMESTAMP"),
1171            Self::Duration => write!(f, "DURATION"),
1172            Self::List => write!(f, "LIST"),
1173            Self::ListTyped(elem) => write!(f, "LIST<{elem}>"),
1174            Self::Map => write!(f, "MAP"),
1175            Self::Bytes => write!(f, "BYTES"),
1176            Self::Node => write!(f, "NODE"),
1177            Self::Edge => write!(f, "EDGE"),
1178            Self::Any => write!(f, "ANY"),
1179        }
1180    }
1181}
1182
1183/// A typed property within a node or edge type definition.
1184#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1185pub struct TypedProperty {
1186    /// Property name.
1187    pub name: String,
1188    /// Expected data type.
1189    pub data_type: PropertyDataType,
1190    /// Whether NULL values are allowed.
1191    pub nullable: bool,
1192    /// Default value (used when property is not explicitly set).
1193    pub default_value: Option<Value>,
1194}
1195
1196/// A constraint on a node or edge type.
1197#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1198pub enum TypeConstraint {
1199    /// Primary key (implies UNIQUE + NOT NULL).
1200    PrimaryKey(Vec<String>),
1201    /// Uniqueness constraint on one or more properties.
1202    Unique(Vec<String>),
1203    /// NOT NULL constraint on a single property.
1204    NotNull(String),
1205    /// CHECK constraint with a named expression string.
1206    Check {
1207        /// Optional constraint name.
1208        name: Option<String>,
1209        /// Expression (stored as string for now).
1210        expression: String,
1211    },
1212}
1213
1214/// Definition of a node type (label schema).
1215#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1216pub struct NodeTypeDefinition {
1217    /// Type name (corresponds to a label).
1218    pub name: String,
1219    /// Typed property definitions.
1220    pub properties: Vec<TypedProperty>,
1221    /// Type-level constraints.
1222    pub constraints: Vec<TypeConstraint>,
1223    /// Parent type names for inheritance (GQL `EXTENDS`).
1224    pub parent_types: Vec<String>,
1225}
1226
1227/// Definition of an edge type (relationship type schema).
1228#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1229pub struct EdgeTypeDefinition {
1230    /// Type name (corresponds to an edge type / relationship type).
1231    pub name: String,
1232    /// Typed property definitions.
1233    pub properties: Vec<TypedProperty>,
1234    /// Type-level constraints.
1235    pub constraints: Vec<TypeConstraint>,
1236    /// Allowed source node types (empty = any).
1237    pub source_node_types: Vec<String>,
1238    /// Allowed target node types (empty = any).
1239    pub target_node_types: Vec<String>,
1240}
1241
1242/// Definition of a graph type (constrains which node/edge types a graph allows).
1243#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1244pub struct GraphTypeDefinition {
1245    /// Graph type name.
1246    pub name: String,
1247    /// Allowed node types (empty = open).
1248    pub allowed_node_types: Vec<String>,
1249    /// Allowed edge types (empty = open).
1250    pub allowed_edge_types: Vec<String>,
1251    /// Whether unlisted types are permitted.
1252    pub open: bool,
1253}
1254
1255/// Definition of a stored procedure.
1256#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1257pub struct ProcedureDefinition {
1258    /// Procedure name.
1259    pub name: String,
1260    /// Parameter definitions: (name, type).
1261    pub params: Vec<(String, String)>,
1262    /// Return column definitions: (name, type).
1263    pub returns: Vec<(String, String)>,
1264    /// Raw GQL query body.
1265    pub body: String,
1266}
1267
1268// === Schema Catalog ===
1269
1270/// Schema constraints and type definitions.
1271pub struct SchemaCatalog {
1272    /// Properties that must be unique for a given label.
1273    unique_constraints: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1274    /// Properties that are required (NOT NULL) for a given label.
1275    required_properties: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1276    /// Registered node type definitions.
1277    node_types: RwLock<HashMap<String, NodeTypeDefinition>>,
1278    /// Registered edge type definitions.
1279    edge_types: RwLock<HashMap<String, EdgeTypeDefinition>>,
1280    /// Registered graph type definitions.
1281    graph_types: RwLock<HashMap<String, GraphTypeDefinition>>,
1282    /// Schema namespaces.
1283    schemas: RwLock<Vec<String>>,
1284    /// Graph instance to graph type bindings.
1285    graph_type_bindings: RwLock<HashMap<String, String>>,
1286    /// Stored procedure definitions.
1287    procedures: RwLock<HashMap<String, ProcedureDefinition>>,
1288}
1289
1290impl SchemaCatalog {
1291    fn new() -> Self {
1292        Self {
1293            unique_constraints: RwLock::new(HashSet::new()),
1294            required_properties: RwLock::new(HashSet::new()),
1295            node_types: RwLock::new(HashMap::new()),
1296            edge_types: RwLock::new(HashMap::new()),
1297            graph_types: RwLock::new(HashMap::new()),
1298            schemas: RwLock::new(Vec::new()),
1299            graph_type_bindings: RwLock::new(HashMap::new()),
1300            procedures: RwLock::new(HashMap::new()),
1301        }
1302    }
1303
1304    // --- Node type operations ---
1305
1306    /// Registers a new node type definition.
1307    ///
1308    /// # Errors
1309    ///
1310    /// Returns `CatalogError::TypeAlreadyExists` if a type with the same name exists.
1311    pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
1312        let mut types = self.node_types.write();
1313        if types.contains_key(&def.name) {
1314            return Err(CatalogError::TypeAlreadyExists(def.name));
1315        }
1316        types.insert(def.name.clone(), def);
1317        Ok(())
1318    }
1319
1320    /// Registers or replaces a node type definition.
1321    pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
1322        self.node_types.write().insert(def.name.clone(), def);
1323    }
1324
1325    /// Drops a node type definition by name.
1326    ///
1327    /// # Errors
1328    ///
1329    /// Returns `CatalogError::TypeNotFound` if no type with the given name exists.
1330    pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
1331        let mut types = self.node_types.write();
1332        if types.remove(name).is_none() {
1333            return Err(CatalogError::TypeNotFound(name.to_string()));
1334        }
1335        Ok(())
1336    }
1337
1338    /// Gets a node type definition by name.
1339    #[must_use]
1340    pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1341        self.node_types.read().get(name).cloned()
1342    }
1343
1344    /// Gets a resolved node type with inherited properties and constraints from parents.
1345    ///
1346    /// Walks the parent chain depth-first, collecting properties and constraints.
1347    /// Detects cycles via a visited set. Child properties override parent ones
1348    /// with the same name.
1349    #[must_use]
1350    pub fn resolved_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1351        let types = self.node_types.read();
1352        let base = types.get(name)?;
1353        if base.parent_types.is_empty() {
1354            return Some(base.clone());
1355        }
1356        let mut visited = HashSet::new();
1357        visited.insert(name.to_string());
1358        let mut all_properties = Vec::new();
1359        let mut all_constraints = Vec::new();
1360        Self::collect_inherited(
1361            &types,
1362            name,
1363            &mut visited,
1364            &mut all_properties,
1365            &mut all_constraints,
1366        );
1367        Some(NodeTypeDefinition {
1368            name: base.name.clone(),
1369            properties: all_properties,
1370            constraints: all_constraints,
1371            parent_types: base.parent_types.clone(),
1372        })
1373    }
1374
1375    /// Recursively collects properties and constraints from a type and its parents.
1376    fn collect_inherited(
1377        types: &HashMap<String, NodeTypeDefinition>,
1378        name: &str,
1379        visited: &mut HashSet<String>,
1380        properties: &mut Vec<TypedProperty>,
1381        constraints: &mut Vec<TypeConstraint>,
1382    ) {
1383        let Some(def) = types.get(name) else { return };
1384        // Walk parents first (depth-first) so child properties override
1385        for parent in &def.parent_types {
1386            if visited.insert(parent.clone()) {
1387                Self::collect_inherited(types, parent, visited, properties, constraints);
1388            }
1389        }
1390        // Add own properties, overriding parent ones with same name
1391        for prop in &def.properties {
1392            if let Some(pos) = properties.iter().position(|p| p.name == prop.name) {
1393                properties[pos] = prop.clone();
1394            } else {
1395                properties.push(prop.clone());
1396            }
1397        }
1398        // Append own constraints (no dedup, constraints are additive)
1399        constraints.extend(def.constraints.iter().cloned());
1400    }
1401
1402    /// Returns all registered node type names.
1403    #[must_use]
1404    pub fn all_node_types(&self) -> Vec<String> {
1405        self.node_types.read().keys().cloned().collect()
1406    }
1407
1408    /// Returns all registered node type definitions.
1409    #[must_use]
1410    pub fn all_node_type_defs(&self) -> Vec<NodeTypeDefinition> {
1411        self.node_types.read().values().cloned().collect()
1412    }
1413
1414    // --- Edge type operations ---
1415
1416    /// Registers a new edge type definition.
1417    ///
1418    /// # Errors
1419    ///
1420    /// Returns `CatalogError::TypeAlreadyExists` if an edge type with the same name exists.
1421    pub fn register_edge_type(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
1422        let mut types = self.edge_types.write();
1423        if types.contains_key(&def.name) {
1424            return Err(CatalogError::TypeAlreadyExists(def.name));
1425        }
1426        types.insert(def.name.clone(), def);
1427        Ok(())
1428    }
1429
1430    /// Registers or replaces an edge type definition.
1431    pub fn register_or_replace_edge_type(&self, def: EdgeTypeDefinition) {
1432        self.edge_types.write().insert(def.name.clone(), def);
1433    }
1434
1435    /// Drops an edge type definition by name.
1436    ///
1437    /// # Errors
1438    ///
1439    /// Returns `CatalogError::TypeNotFound` if no edge type with the given name exists.
1440    pub fn drop_edge_type(&self, name: &str) -> Result<(), CatalogError> {
1441        let mut types = self.edge_types.write();
1442        if types.remove(name).is_none() {
1443            return Err(CatalogError::TypeNotFound(name.to_string()));
1444        }
1445        Ok(())
1446    }
1447
1448    /// Gets an edge type definition by name.
1449    #[must_use]
1450    pub fn get_edge_type(&self, name: &str) -> Option<EdgeTypeDefinition> {
1451        self.edge_types.read().get(name).cloned()
1452    }
1453
1454    /// Returns all registered edge type names.
1455    #[must_use]
1456    pub fn all_edge_types(&self) -> Vec<String> {
1457        self.edge_types.read().keys().cloned().collect()
1458    }
1459
1460    /// Returns all registered edge type definitions.
1461    #[must_use]
1462    pub fn all_edge_type_defs(&self) -> Vec<EdgeTypeDefinition> {
1463        self.edge_types.read().values().cloned().collect()
1464    }
1465
1466    // --- Graph type operations ---
1467
1468    /// Registers a new graph type definition.
1469    ///
1470    /// # Errors
1471    ///
1472    /// Returns `CatalogError::TypeAlreadyExists` if a graph type with the same name exists.
1473    pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
1474        let mut types = self.graph_types.write();
1475        if types.contains_key(&def.name) {
1476            return Err(CatalogError::TypeAlreadyExists(def.name));
1477        }
1478        types.insert(def.name.clone(), def);
1479        Ok(())
1480    }
1481
1482    /// Drops a graph type definition by name.
1483    ///
1484    /// # Errors
1485    ///
1486    /// Returns `CatalogError::TypeNotFound` if no graph type with the given name exists.
1487    pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
1488        let mut types = self.graph_types.write();
1489        if types.remove(name).is_none() {
1490            return Err(CatalogError::TypeNotFound(name.to_string()));
1491        }
1492        Ok(())
1493    }
1494
1495    /// Gets a graph type definition by name.
1496    #[must_use]
1497    pub fn get_graph_type(&self, name: &str) -> Option<GraphTypeDefinition> {
1498        self.graph_types.read().get(name).cloned()
1499    }
1500
1501    /// Returns all registered graph type names.
1502    #[must_use]
1503    pub fn all_graph_types(&self) -> Vec<String> {
1504        self.graph_types.read().keys().cloned().collect()
1505    }
1506
1507    /// Returns all registered graph type definitions.
1508    #[must_use]
1509    pub fn all_graph_type_defs(&self) -> Vec<GraphTypeDefinition> {
1510        self.graph_types.read().values().cloned().collect()
1511    }
1512
1513    // --- Schema namespace operations ---
1514
1515    /// Registers a schema namespace.
1516    ///
1517    /// # Errors
1518    ///
1519    /// Returns `CatalogError::SchemaAlreadyExists` if the namespace already exists.
1520    pub fn register_schema(&self, name: String) -> Result<(), CatalogError> {
1521        let mut schemas = self.schemas.write();
1522        if schemas.contains(&name) {
1523            return Err(CatalogError::SchemaAlreadyExists(name));
1524        }
1525        schemas.push(name);
1526        Ok(())
1527    }
1528
1529    /// Drops a schema namespace.
1530    ///
1531    /// # Errors
1532    ///
1533    /// Returns `CatalogError::SchemaNotFound` if the namespace does not exist.
1534    pub fn drop_schema(&self, name: &str) -> Result<(), CatalogError> {
1535        let mut schemas = self.schemas.write();
1536        if let Some(pos) = schemas.iter().position(|s| s == name) {
1537            schemas.remove(pos);
1538            Ok(())
1539        } else {
1540            Err(CatalogError::SchemaNotFound(name.to_string()))
1541        }
1542    }
1543
1544    /// Checks whether a schema namespace exists.
1545    #[must_use]
1546    pub fn schema_exists(&self, name: &str) -> bool {
1547        self.schemas
1548            .read()
1549            .iter()
1550            .any(|s| s.eq_ignore_ascii_case(name))
1551    }
1552
1553    /// Returns all registered schema namespace names.
1554    #[must_use]
1555    pub fn schema_names(&self) -> Vec<String> {
1556        self.schemas.read().clone()
1557    }
1558
1559    // --- ALTER operations ---
1560
1561    /// Adds a constraint to an existing node type, creating a minimal type if needed.
1562    ///
1563    /// # Errors
1564    ///
1565    /// Currently infallible, but returns `Result` for forward compatibility.
1566    pub fn add_constraint_to_type(
1567        &self,
1568        label: &str,
1569        constraint: TypeConstraint,
1570    ) -> Result<(), CatalogError> {
1571        let mut types = self.node_types.write();
1572        if let Some(def) = types.get_mut(label) {
1573            def.constraints.push(constraint);
1574        } else {
1575            // Auto-create a minimal type definition for the label
1576            types.insert(
1577                label.to_string(),
1578                NodeTypeDefinition {
1579                    name: label.to_string(),
1580                    properties: Vec::new(),
1581                    constraints: vec![constraint],
1582                    parent_types: Vec::new(),
1583                },
1584            );
1585        }
1586        Ok(())
1587    }
1588
1589    /// Adds a property to an existing node type.
1590    ///
1591    /// # Errors
1592    ///
1593    /// * `CatalogError::TypeNotFound` if the node type does not exist.
1594    /// * `CatalogError::TypeAlreadyExists` if the property already exists on the type.
1595    pub fn alter_node_type_add_property(
1596        &self,
1597        type_name: &str,
1598        property: TypedProperty,
1599    ) -> Result<(), CatalogError> {
1600        let mut types = self.node_types.write();
1601        let def = types
1602            .get_mut(type_name)
1603            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1604        if def.properties.iter().any(|p| p.name == property.name) {
1605            return Err(CatalogError::TypeAlreadyExists(format!(
1606                "property {} on {}",
1607                property.name, type_name
1608            )));
1609        }
1610        def.properties.push(property);
1611        Ok(())
1612    }
1613
1614    /// Drops a property from an existing node type.
1615    ///
1616    /// # Errors
1617    ///
1618    /// Returns `CatalogError::TypeNotFound` if the node type or property does not exist.
1619    pub fn alter_node_type_drop_property(
1620        &self,
1621        type_name: &str,
1622        property_name: &str,
1623    ) -> Result<(), CatalogError> {
1624        let mut types = self.node_types.write();
1625        let def = types
1626            .get_mut(type_name)
1627            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1628        let len_before = def.properties.len();
1629        def.properties.retain(|p| p.name != property_name);
1630        if def.properties.len() == len_before {
1631            return Err(CatalogError::TypeNotFound(format!(
1632                "property {} on {}",
1633                property_name, type_name
1634            )));
1635        }
1636        Ok(())
1637    }
1638
1639    /// Adds a property to an existing edge type.
1640    ///
1641    /// # Errors
1642    ///
1643    /// * `CatalogError::TypeNotFound` if the edge type does not exist.
1644    /// * `CatalogError::TypeAlreadyExists` if the property already exists on the type.
1645    pub fn alter_edge_type_add_property(
1646        &self,
1647        type_name: &str,
1648        property: TypedProperty,
1649    ) -> Result<(), CatalogError> {
1650        let mut types = self.edge_types.write();
1651        let def = types
1652            .get_mut(type_name)
1653            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1654        if def.properties.iter().any(|p| p.name == property.name) {
1655            return Err(CatalogError::TypeAlreadyExists(format!(
1656                "property {} on {}",
1657                property.name, type_name
1658            )));
1659        }
1660        def.properties.push(property);
1661        Ok(())
1662    }
1663
1664    /// Drops a property from an existing edge type.
1665    ///
1666    /// # Errors
1667    ///
1668    /// Returns `CatalogError::TypeNotFound` if the edge type or property does not exist.
1669    pub fn alter_edge_type_drop_property(
1670        &self,
1671        type_name: &str,
1672        property_name: &str,
1673    ) -> Result<(), CatalogError> {
1674        let mut types = self.edge_types.write();
1675        let def = types
1676            .get_mut(type_name)
1677            .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1678        let len_before = def.properties.len();
1679        def.properties.retain(|p| p.name != property_name);
1680        if def.properties.len() == len_before {
1681            return Err(CatalogError::TypeNotFound(format!(
1682                "property {} on {}",
1683                property_name, type_name
1684            )));
1685        }
1686        Ok(())
1687    }
1688
1689    /// Adds a node type to a graph type.
1690    ///
1691    /// # Errors
1692    ///
1693    /// Returns `CatalogError::TypeNotFound` if the graph type does not exist.
1694    pub fn alter_graph_type_add_node_type(
1695        &self,
1696        graph_type_name: &str,
1697        node_type: String,
1698    ) -> Result<(), CatalogError> {
1699        let mut types = self.graph_types.write();
1700        let def = types
1701            .get_mut(graph_type_name)
1702            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1703        if !def.allowed_node_types.contains(&node_type) {
1704            def.allowed_node_types.push(node_type);
1705        }
1706        Ok(())
1707    }
1708
1709    /// Drops a node type from a graph type.
1710    ///
1711    /// # Errors
1712    ///
1713    /// Returns `CatalogError::TypeNotFound` if the graph type does not exist.
1714    pub fn alter_graph_type_drop_node_type(
1715        &self,
1716        graph_type_name: &str,
1717        node_type: &str,
1718    ) -> Result<(), CatalogError> {
1719        let mut types = self.graph_types.write();
1720        let def = types
1721            .get_mut(graph_type_name)
1722            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1723        def.allowed_node_types.retain(|t| t != node_type);
1724        Ok(())
1725    }
1726
1727    /// Adds an edge type to a graph type.
1728    ///
1729    /// # Errors
1730    ///
1731    /// Returns `CatalogError::TypeNotFound` if the graph type does not exist.
1732    pub fn alter_graph_type_add_edge_type(
1733        &self,
1734        graph_type_name: &str,
1735        edge_type: String,
1736    ) -> Result<(), CatalogError> {
1737        let mut types = self.graph_types.write();
1738        let def = types
1739            .get_mut(graph_type_name)
1740            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1741        if !def.allowed_edge_types.contains(&edge_type) {
1742            def.allowed_edge_types.push(edge_type);
1743        }
1744        Ok(())
1745    }
1746
1747    /// Drops an edge type from a graph type.
1748    ///
1749    /// # Errors
1750    ///
1751    /// Returns `CatalogError::TypeNotFound` if the graph type does not exist.
1752    pub fn alter_graph_type_drop_edge_type(
1753        &self,
1754        graph_type_name: &str,
1755        edge_type: &str,
1756    ) -> Result<(), CatalogError> {
1757        let mut types = self.graph_types.write();
1758        let def = types
1759            .get_mut(graph_type_name)
1760            .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1761        def.allowed_edge_types.retain(|t| t != edge_type);
1762        Ok(())
1763    }
1764
1765    // --- Procedure operations ---
1766
1767    /// Registers a stored procedure.
1768    ///
1769    /// # Errors
1770    ///
1771    /// Returns `CatalogError::TypeAlreadyExists` if a procedure with the same name exists.
1772    pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
1773        let mut procs = self.procedures.write();
1774        if procs.contains_key(&def.name) {
1775            return Err(CatalogError::TypeAlreadyExists(def.name.clone()));
1776        }
1777        procs.insert(def.name.clone(), def);
1778        Ok(())
1779    }
1780
1781    /// Replaces or creates a stored procedure.
1782    pub fn replace_procedure(&self, def: ProcedureDefinition) {
1783        self.procedures.write().insert(def.name.clone(), def);
1784    }
1785
1786    /// Drops a stored procedure.
1787    ///
1788    /// # Errors
1789    ///
1790    /// Returns `CatalogError::TypeNotFound` if no procedure with the given name exists.
1791    pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
1792        let mut procs = self.procedures.write();
1793        if procs.remove(name).is_none() {
1794            return Err(CatalogError::TypeNotFound(name.to_string()));
1795        }
1796        Ok(())
1797    }
1798
1799    /// Gets a stored procedure by name.
1800    pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
1801        self.procedures.read().get(name).cloned()
1802    }
1803
1804    /// Returns all registered procedure definitions.
1805    #[must_use]
1806    pub fn all_procedure_defs(&self) -> Vec<ProcedureDefinition> {
1807        self.procedures.read().values().cloned().collect()
1808    }
1809
1810    /// Returns all graph type bindings (graph_name, type_name).
1811    #[must_use]
1812    pub fn all_graph_type_bindings(&self) -> Vec<(String, String)> {
1813        self.graph_type_bindings
1814            .read()
1815            .iter()
1816            .map(|(k, v)| (k.clone(), v.clone()))
1817            .collect()
1818    }
1819
1820    fn add_unique_constraint(
1821        &self,
1822        label: LabelId,
1823        property_key: PropertyKeyId,
1824    ) -> Result<(), CatalogError> {
1825        let mut constraints = self.unique_constraints.write();
1826        let key = (label, property_key);
1827        if !constraints.insert(key) {
1828            return Err(CatalogError::ConstraintAlreadyExists);
1829        }
1830        Ok(())
1831    }
1832
1833    fn add_required_property(
1834        &self,
1835        label: LabelId,
1836        property_key: PropertyKeyId,
1837    ) -> Result<(), CatalogError> {
1838        let mut required = self.required_properties.write();
1839        let key = (label, property_key);
1840        if !required.insert(key) {
1841            return Err(CatalogError::ConstraintAlreadyExists);
1842        }
1843        Ok(())
1844    }
1845
1846    fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1847        self.required_properties
1848            .read()
1849            .contains(&(label, property_key))
1850    }
1851
1852    fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1853        self.unique_constraints
1854            .read()
1855            .contains(&(label, property_key))
1856    }
1857}
1858
1859// === Errors ===
1860
1861/// Catalog-related errors.
1862#[derive(Debug, Clone, PartialEq, Eq)]
1863pub enum CatalogError {
1864    /// Schema constraints are not enabled.
1865    SchemaNotEnabled,
1866    /// The constraint already exists.
1867    ConstraintAlreadyExists,
1868    /// The label does not exist.
1869    LabelNotFound(String),
1870    /// The property key does not exist.
1871    PropertyKeyNotFound(String),
1872    /// The edge type does not exist.
1873    EdgeTypeNotFound(String),
1874    /// The index does not exist.
1875    IndexNotFound(IndexId),
1876    /// A type with this name already exists.
1877    TypeAlreadyExists(String),
1878    /// No type with this name exists.
1879    TypeNotFound(String),
1880    /// A schema with this name already exists.
1881    SchemaAlreadyExists(String),
1882    /// No schema with this name exists.
1883    SchemaNotFound(String),
1884}
1885
1886impl std::fmt::Display for CatalogError {
1887    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1888        match self {
1889            Self::SchemaNotEnabled => write!(f, "Schema constraints are not enabled"),
1890            Self::ConstraintAlreadyExists => write!(f, "Constraint already exists"),
1891            Self::LabelNotFound(name) => write!(f, "Label not found: {name}"),
1892            Self::PropertyKeyNotFound(name) => write!(f, "Property key not found: {name}"),
1893            Self::EdgeTypeNotFound(name) => write!(f, "Edge type not found: {name}"),
1894            Self::IndexNotFound(id) => write!(f, "Index not found: {id}"),
1895            Self::TypeAlreadyExists(name) => write!(f, "Type already exists: {name}"),
1896            Self::TypeNotFound(name) => write!(f, "Type not found: {name}"),
1897            Self::SchemaAlreadyExists(name) => write!(f, "Schema already exists: {name}"),
1898            Self::SchemaNotFound(name) => write!(f, "Schema not found: {name}"),
1899        }
1900    }
1901}
1902
1903impl std::error::Error for CatalogError {}
1904
1905// === Constraint Validator ===
1906
1907use grafeo_core::execution::operators::ConstraintValidator;
1908use grafeo_core::execution::operators::OperatorError;
1909
1910/// Validates schema constraints during mutation operations using the Catalog.
1911///
1912/// Checks type definitions, NOT NULL constraints, and UNIQUE constraints
1913/// against registered node/edge type definitions.
1914pub struct CatalogConstraintValidator {
1915    catalog: Arc<Catalog>,
1916    /// Optional graph name for graph-type-bound validation.
1917    graph_name: Option<String>,
1918    /// Optional graph store for UNIQUE constraint enforcement via index lookup.
1919    store: Option<Arc<dyn grafeo_core::graph::GraphStore>>,
1920}
1921
1922impl CatalogConstraintValidator {
1923    /// Creates a new validator wrapping the given catalog.
1924    pub fn new(catalog: Arc<Catalog>) -> Self {
1925        Self {
1926            catalog,
1927            graph_name: None,
1928            store: None,
1929        }
1930    }
1931
1932    /// Sets the graph name for graph-type-bound validation.
1933    pub fn with_graph_name(mut self, name: String) -> Self {
1934        self.graph_name = Some(name);
1935        self
1936    }
1937
1938    /// Attaches a graph store for UNIQUE constraint enforcement.
1939    pub fn with_store(mut self, store: Arc<dyn grafeo_core::graph::GraphStore>) -> Self {
1940        self.store = Some(store);
1941        self
1942    }
1943}
1944
1945impl ConstraintValidator for CatalogConstraintValidator {
1946    fn validate_node_property(
1947        &self,
1948        labels: &[String],
1949        key: &str,
1950        value: &Value,
1951    ) -> Result<(), OperatorError> {
1952        for label in labels {
1953            if let Some(type_def) = self.catalog.resolved_node_type(label)
1954                && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1955            {
1956                // Check NOT NULL
1957                if !typed_prop.nullable && *value == Value::Null {
1958                    return Err(OperatorError::ConstraintViolation(format!(
1959                        "property '{key}' on :{label} is NOT NULL, cannot set to null"
1960                    )));
1961                }
1962                // Check type compatibility
1963                if *value != Value::Null && !typed_prop.data_type.matches(value) {
1964                    return Err(OperatorError::ConstraintViolation(format!(
1965                        "property '{key}' on :{label} expects {:?}, got {:?}",
1966                        typed_prop.data_type, value
1967                    )));
1968                }
1969            }
1970        }
1971        Ok(())
1972    }
1973
1974    fn validate_node_complete(
1975        &self,
1976        labels: &[String],
1977        properties: &[(String, Value)],
1978    ) -> Result<(), OperatorError> {
1979        let prop_names: std::collections::HashSet<&str> =
1980            properties.iter().map(|(n, _)| n.as_str()).collect();
1981
1982        for label in labels {
1983            if let Some(type_def) = self.catalog.resolved_node_type(label) {
1984                // Check that all NOT NULL properties are present
1985                for typed_prop in &type_def.properties {
1986                    if !typed_prop.nullable
1987                        && typed_prop.default_value.is_none()
1988                        && !prop_names.contains(typed_prop.name.as_str())
1989                    {
1990                        return Err(OperatorError::ConstraintViolation(format!(
1991                            "missing required property '{}' on :{label}",
1992                            typed_prop.name
1993                        )));
1994                    }
1995                }
1996                // Check type-level constraints
1997                for constraint in &type_def.constraints {
1998                    match constraint {
1999                        TypeConstraint::NotNull(prop_name) => {
2000                            if !prop_names.contains(prop_name.as_str()) {
2001                                return Err(OperatorError::ConstraintViolation(format!(
2002                                    "missing required property '{prop_name}' on :{label} (NOT NULL constraint)"
2003                                )));
2004                            }
2005                        }
2006                        TypeConstraint::PrimaryKey(key_props) => {
2007                            for pk in key_props {
2008                                if !prop_names.contains(pk.as_str()) {
2009                                    return Err(OperatorError::ConstraintViolation(format!(
2010                                        "missing primary key property '{pk}' on :{label}"
2011                                    )));
2012                                }
2013                            }
2014                        }
2015                        TypeConstraint::Check { name, expression } => {
2016                            match check_eval::evaluate_check(expression, properties) {
2017                                Ok(true) => {}
2018                                Ok(false) => {
2019                                    let constraint_name = name.as_deref().unwrap_or("unnamed");
2020                                    return Err(OperatorError::ConstraintViolation(format!(
2021                                        "CHECK constraint '{constraint_name}' violated on :{label}"
2022                                    )));
2023                                }
2024                                Err(err) => {
2025                                    return Err(OperatorError::ConstraintViolation(format!(
2026                                        "CHECK constraint evaluation error: {err}"
2027                                    )));
2028                                }
2029                            }
2030                        }
2031                        TypeConstraint::Unique(_) => {}
2032                    }
2033                }
2034            }
2035        }
2036        Ok(())
2037    }
2038
2039    fn check_unique_node_property(
2040        &self,
2041        labels: &[String],
2042        key: &str,
2043        value: &Value,
2044    ) -> Result<(), OperatorError> {
2045        // Skip uniqueness check for NULL values (NULLs are never duplicates)
2046        if *value == Value::Null {
2047            return Ok(());
2048        }
2049        for label in labels {
2050            if let Some(type_def) = self.catalog.resolved_node_type(label) {
2051                for constraint in &type_def.constraints {
2052                    let is_unique = match constraint {
2053                        TypeConstraint::Unique(props) => props.iter().any(|p| p == key),
2054                        TypeConstraint::PrimaryKey(props) => props.iter().any(|p| p == key),
2055                        _ => false,
2056                    };
2057                    if is_unique && let Some(ref store) = self.store {
2058                        let existing = store.find_nodes_by_property(key, value);
2059                        for node_id in existing {
2060                            if let Some(node) = store.get_node(node_id) {
2061                                let has_label = node.labels.iter().any(|l| l.as_str() == label);
2062                                if has_label {
2063                                    return Err(OperatorError::ConstraintViolation(format!(
2064                                        "UNIQUE constraint violation: property '{key}' \
2065                                             with value {value:?} already exists on :{label}"
2066                                    )));
2067                                }
2068                            }
2069                        }
2070                    }
2071                }
2072            }
2073        }
2074        Ok(())
2075    }
2076
2077    fn validate_edge_property(
2078        &self,
2079        edge_type: &str,
2080        key: &str,
2081        value: &Value,
2082    ) -> Result<(), OperatorError> {
2083        if let Some(type_def) = self.catalog.get_edge_type_def(edge_type)
2084            && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
2085        {
2086            // Check NOT NULL
2087            if !typed_prop.nullable && *value == Value::Null {
2088                return Err(OperatorError::ConstraintViolation(format!(
2089                    "property '{key}' on :{edge_type} is NOT NULL, cannot set to null"
2090                )));
2091            }
2092            // Check type compatibility
2093            if *value != Value::Null && !typed_prop.data_type.matches(value) {
2094                return Err(OperatorError::ConstraintViolation(format!(
2095                    "property '{key}' on :{edge_type} expects {:?}, got {:?}",
2096                    typed_prop.data_type, value
2097                )));
2098            }
2099        }
2100        Ok(())
2101    }
2102
2103    fn validate_edge_complete(
2104        &self,
2105        edge_type: &str,
2106        properties: &[(String, Value)],
2107    ) -> Result<(), OperatorError> {
2108        if let Some(type_def) = self.catalog.get_edge_type_def(edge_type) {
2109            let prop_names: std::collections::HashSet<&str> =
2110                properties.iter().map(|(n, _)| n.as_str()).collect();
2111
2112            for typed_prop in &type_def.properties {
2113                if !typed_prop.nullable
2114                    && typed_prop.default_value.is_none()
2115                    && !prop_names.contains(typed_prop.name.as_str())
2116                {
2117                    return Err(OperatorError::ConstraintViolation(format!(
2118                        "missing required property '{}' on :{edge_type}",
2119                        typed_prop.name
2120                    )));
2121                }
2122            }
2123
2124            for constraint in &type_def.constraints {
2125                if let TypeConstraint::Check { name, expression } = constraint {
2126                    match check_eval::evaluate_check(expression, properties) {
2127                        Ok(true) => {}
2128                        Ok(false) => {
2129                            let constraint_name = name.as_deref().unwrap_or("unnamed");
2130                            return Err(OperatorError::ConstraintViolation(format!(
2131                                "CHECK constraint '{constraint_name}' violated on :{edge_type}"
2132                            )));
2133                        }
2134                        Err(err) => {
2135                            return Err(OperatorError::ConstraintViolation(format!(
2136                                "CHECK constraint evaluation error: {err}"
2137                            )));
2138                        }
2139                    }
2140                }
2141            }
2142        }
2143        Ok(())
2144    }
2145
2146    fn validate_node_labels_allowed(&self, labels: &[String]) -> Result<(), OperatorError> {
2147        let Some(ref graph_name) = self.graph_name else {
2148            return Ok(());
2149        };
2150        let Some(type_name) = self.catalog.get_graph_type_binding(graph_name) else {
2151            return Ok(());
2152        };
2153        let Some(gt) = self
2154            .catalog
2155            .schema()
2156            .and_then(|s| s.get_graph_type(&type_name))
2157        else {
2158            return Ok(());
2159        };
2160        if !gt.open && !gt.allowed_node_types.is_empty() {
2161            let allowed = labels
2162                .iter()
2163                .any(|l| gt.allowed_node_types.iter().any(|a| a == l));
2164            if !allowed {
2165                return Err(OperatorError::ConstraintViolation(format!(
2166                    "node labels {labels:?} are not allowed by graph type '{}'",
2167                    gt.name
2168                )));
2169            }
2170        }
2171        Ok(())
2172    }
2173
2174    fn validate_edge_type_allowed(&self, edge_type: &str) -> Result<(), OperatorError> {
2175        let Some(ref graph_name) = self.graph_name else {
2176            return Ok(());
2177        };
2178        let Some(type_name) = self.catalog.get_graph_type_binding(graph_name) else {
2179            return Ok(());
2180        };
2181        let Some(gt) = self
2182            .catalog
2183            .schema()
2184            .and_then(|s| s.get_graph_type(&type_name))
2185        else {
2186            return Ok(());
2187        };
2188        if !gt.open && !gt.allowed_edge_types.is_empty() {
2189            let allowed = gt.allowed_edge_types.iter().any(|a| a == edge_type);
2190            if !allowed {
2191                return Err(OperatorError::ConstraintViolation(format!(
2192                    "edge type '{edge_type}' is not allowed by graph type '{}'",
2193                    gt.name
2194                )));
2195            }
2196        }
2197        Ok(())
2198    }
2199
2200    fn validate_edge_endpoints(
2201        &self,
2202        edge_type: &str,
2203        source_labels: &[String],
2204        target_labels: &[String],
2205    ) -> Result<(), OperatorError> {
2206        let Some(type_def) = self.catalog.get_edge_type_def(edge_type) else {
2207            return Ok(());
2208        };
2209        if !type_def.source_node_types.is_empty() {
2210            let source_ok = source_labels
2211                .iter()
2212                .any(|l| type_def.source_node_types.iter().any(|s| s == l));
2213            if !source_ok {
2214                return Err(OperatorError::ConstraintViolation(format!(
2215                    "source node labels {source_labels:?} are not allowed for edge type '{edge_type}', \
2216                     expected one of {:?}",
2217                    type_def.source_node_types
2218                )));
2219            }
2220        }
2221        if !type_def.target_node_types.is_empty() {
2222            let target_ok = target_labels
2223                .iter()
2224                .any(|l| type_def.target_node_types.iter().any(|t| t == l));
2225            if !target_ok {
2226                return Err(OperatorError::ConstraintViolation(format!(
2227                    "target node labels {target_labels:?} are not allowed for edge type '{edge_type}', \
2228                     expected one of {:?}",
2229                    type_def.target_node_types
2230                )));
2231            }
2232        }
2233        Ok(())
2234    }
2235
2236    fn inject_defaults(&self, labels: &[String], properties: &mut Vec<(String, Value)>) {
2237        for label in labels {
2238            if let Some(type_def) = self.catalog.resolved_node_type(label) {
2239                for typed_prop in &type_def.properties {
2240                    if let Some(ref default) = typed_prop.default_value {
2241                        let already_set = properties.iter().any(|(n, _)| n == &typed_prop.name);
2242                        if !already_set {
2243                            properties.push((typed_prop.name.clone(), default.clone()));
2244                        }
2245                    }
2246                }
2247            }
2248        }
2249    }
2250}
2251
2252#[cfg(test)]
2253mod tests {
2254    use super::*;
2255    use std::thread;
2256
2257    #[test]
2258    fn test_catalog_labels() {
2259        let catalog = Catalog::new();
2260
2261        // Get or create labels
2262        let person_id = catalog.get_or_create_label("Person");
2263        let company_id = catalog.get_or_create_label("Company");
2264
2265        // IDs should be different
2266        assert_ne!(person_id, company_id);
2267
2268        // Getting the same label should return the same ID
2269        assert_eq!(catalog.get_or_create_label("Person"), person_id);
2270
2271        // Should be able to look up by name
2272        assert_eq!(catalog.get_label_id("Person"), Some(person_id));
2273        assert_eq!(catalog.get_label_id("Company"), Some(company_id));
2274        assert_eq!(catalog.get_label_id("Unknown"), None);
2275
2276        // Should be able to look up by ID
2277        assert_eq!(catalog.get_label_name(person_id).as_deref(), Some("Person"));
2278        assert_eq!(
2279            catalog.get_label_name(company_id).as_deref(),
2280            Some("Company")
2281        );
2282
2283        // Count should be correct
2284        assert_eq!(catalog.label_count(), 2);
2285    }
2286
2287    #[test]
2288    fn test_catalog_property_keys() {
2289        let catalog = Catalog::new();
2290
2291        let name_id = catalog.get_or_create_property_key("name");
2292        let age_id = catalog.get_or_create_property_key("age");
2293
2294        assert_ne!(name_id, age_id);
2295        assert_eq!(catalog.get_or_create_property_key("name"), name_id);
2296        assert_eq!(catalog.get_property_key_id("name"), Some(name_id));
2297        assert_eq!(
2298            catalog.get_property_key_name(name_id).as_deref(),
2299            Some("name")
2300        );
2301        assert_eq!(catalog.property_key_count(), 2);
2302    }
2303
2304    #[test]
2305    fn test_catalog_edge_types() {
2306        let catalog = Catalog::new();
2307
2308        let knows_id = catalog.get_or_create_edge_type("KNOWS");
2309        let works_at_id = catalog.get_or_create_edge_type("WORKS_AT");
2310
2311        assert_ne!(knows_id, works_at_id);
2312        assert_eq!(catalog.get_or_create_edge_type("KNOWS"), knows_id);
2313        assert_eq!(catalog.get_edge_type_id("KNOWS"), Some(knows_id));
2314        assert_eq!(
2315            catalog.get_edge_type_name(knows_id).as_deref(),
2316            Some("KNOWS")
2317        );
2318        assert_eq!(catalog.edge_type_count(), 2);
2319    }
2320
2321    #[test]
2322    fn test_catalog_indexes() {
2323        let catalog = Catalog::new();
2324
2325        let person_id = catalog.get_or_create_label("Person");
2326        let name_id = catalog.get_or_create_property_key("name");
2327        let age_id = catalog.get_or_create_property_key("age");
2328
2329        // Create indexes
2330        let idx1 = catalog.create_index("idx_person_name", person_id, name_id, IndexType::Hash);
2331        let idx2 = catalog.create_index("idx_person_age", person_id, age_id, IndexType::BTree);
2332
2333        assert_ne!(idx1, idx2);
2334        assert_eq!(catalog.index_count(), 2);
2335
2336        // Look up by label
2337        let label_indexes = catalog.indexes_for_label(person_id);
2338        assert_eq!(label_indexes.len(), 2);
2339        assert!(label_indexes.contains(&idx1));
2340        assert!(label_indexes.contains(&idx2));
2341
2342        // Look up by label and property
2343        let name_indexes = catalog.indexes_for_label_property(person_id, name_id);
2344        assert_eq!(name_indexes.len(), 1);
2345        assert_eq!(name_indexes[0], idx1);
2346
2347        // Get definition
2348        let def = catalog.get_index(idx1).unwrap();
2349        assert_eq!(def.label, person_id);
2350        assert_eq!(def.property_key, name_id);
2351        assert_eq!(def.index_type, IndexType::Hash);
2352
2353        // Drop index
2354        assert!(catalog.drop_index(idx1));
2355        assert_eq!(catalog.index_count(), 1);
2356        assert!(catalog.get_index(idx1).is_none());
2357        assert_eq!(catalog.indexes_for_label(person_id).len(), 1);
2358    }
2359
2360    #[test]
2361    fn test_catalog_schema_constraints() {
2362        let catalog = Catalog::with_schema();
2363
2364        let person_id = catalog.get_or_create_label("Person");
2365        let email_id = catalog.get_or_create_property_key("email");
2366        let name_id = catalog.get_or_create_property_key("name");
2367
2368        // Add constraints
2369        assert!(catalog.add_unique_constraint(person_id, email_id).is_ok());
2370        assert!(catalog.add_required_property(person_id, name_id).is_ok());
2371
2372        // Check constraints
2373        assert!(catalog.is_property_unique(person_id, email_id));
2374        assert!(!catalog.is_property_unique(person_id, name_id));
2375        assert!(catalog.is_property_required(person_id, name_id));
2376        assert!(!catalog.is_property_required(person_id, email_id));
2377
2378        // Duplicate constraint should fail
2379        assert_eq!(
2380            catalog.add_unique_constraint(person_id, email_id),
2381            Err(CatalogError::ConstraintAlreadyExists)
2382        );
2383    }
2384
2385    #[test]
2386    fn test_catalog_schema_always_enabled() {
2387        // Catalog::new() always enables schema
2388        let catalog = Catalog::new();
2389        assert!(catalog.has_schema());
2390
2391        let person_id = catalog.get_or_create_label("Person");
2392        let email_id = catalog.get_or_create_property_key("email");
2393
2394        // Should succeed with schema enabled
2395        assert_eq!(catalog.add_unique_constraint(person_id, email_id), Ok(()));
2396    }
2397
2398    // === Additional tests for comprehensive coverage ===
2399
2400    #[test]
2401    fn test_catalog_default() {
2402        let catalog = Catalog::default();
2403        assert!(catalog.has_schema());
2404        assert_eq!(catalog.label_count(), 0);
2405        assert_eq!(catalog.property_key_count(), 0);
2406        assert_eq!(catalog.edge_type_count(), 0);
2407        assert_eq!(catalog.index_count(), 0);
2408    }
2409
2410    #[test]
2411    fn test_catalog_all_labels() {
2412        let catalog = Catalog::new();
2413
2414        catalog.get_or_create_label("Person");
2415        catalog.get_or_create_label("Company");
2416        catalog.get_or_create_label("Product");
2417
2418        let all = catalog.all_labels();
2419        assert_eq!(all.len(), 3);
2420        assert!(all.iter().any(|l| l.as_ref() == "Person"));
2421        assert!(all.iter().any(|l| l.as_ref() == "Company"));
2422        assert!(all.iter().any(|l| l.as_ref() == "Product"));
2423    }
2424
2425    #[test]
2426    fn test_catalog_all_property_keys() {
2427        let catalog = Catalog::new();
2428
2429        catalog.get_or_create_property_key("name");
2430        catalog.get_or_create_property_key("age");
2431        catalog.get_or_create_property_key("email");
2432
2433        let all = catalog.all_property_keys();
2434        assert_eq!(all.len(), 3);
2435        assert!(all.iter().any(|k| k.as_ref() == "name"));
2436        assert!(all.iter().any(|k| k.as_ref() == "age"));
2437        assert!(all.iter().any(|k| k.as_ref() == "email"));
2438    }
2439
2440    #[test]
2441    fn test_catalog_all_edge_types() {
2442        let catalog = Catalog::new();
2443
2444        catalog.get_or_create_edge_type("KNOWS");
2445        catalog.get_or_create_edge_type("WORKS_AT");
2446        catalog.get_or_create_edge_type("LIVES_IN");
2447
2448        let all = catalog.all_edge_types();
2449        assert_eq!(all.len(), 3);
2450        assert!(all.iter().any(|t| t.as_ref() == "KNOWS"));
2451        assert!(all.iter().any(|t| t.as_ref() == "WORKS_AT"));
2452        assert!(all.iter().any(|t| t.as_ref() == "LIVES_IN"));
2453    }
2454
2455    #[test]
2456    fn test_catalog_invalid_id_lookup() {
2457        let catalog = Catalog::new();
2458
2459        // Create one label to ensure IDs are allocated
2460        let _ = catalog.get_or_create_label("Person");
2461
2462        // Try to look up non-existent IDs
2463        let invalid_label = LabelId::new(999);
2464        let invalid_property = PropertyKeyId::new(999);
2465        let invalid_edge_type = EdgeTypeId::new(999);
2466        let invalid_index = IndexId::new(999);
2467
2468        assert!(catalog.get_label_name(invalid_label).is_none());
2469        assert!(catalog.get_property_key_name(invalid_property).is_none());
2470        assert!(catalog.get_edge_type_name(invalid_edge_type).is_none());
2471        assert!(catalog.get_index(invalid_index).is_none());
2472    }
2473
2474    #[test]
2475    fn test_catalog_drop_nonexistent_index() {
2476        let catalog = Catalog::new();
2477        let invalid_index = IndexId::new(999);
2478        assert!(!catalog.drop_index(invalid_index));
2479    }
2480
2481    #[test]
2482    fn test_catalog_indexes_for_nonexistent_label() {
2483        let catalog = Catalog::new();
2484        let invalid_label = LabelId::new(999);
2485        let invalid_property = PropertyKeyId::new(999);
2486
2487        assert!(catalog.indexes_for_label(invalid_label).is_empty());
2488        assert!(
2489            catalog
2490                .indexes_for_label_property(invalid_label, invalid_property)
2491                .is_empty()
2492        );
2493    }
2494
2495    #[test]
2496    fn test_catalog_multiple_indexes_same_property() {
2497        let catalog = Catalog::new();
2498
2499        let person_id = catalog.get_or_create_label("Person");
2500        let name_id = catalog.get_or_create_property_key("name");
2501
2502        // Create multiple indexes on the same property with different types
2503        let hash_idx = catalog.create_index("idx_hash", person_id, name_id, IndexType::Hash);
2504        let btree_idx = catalog.create_index("idx_btree", person_id, name_id, IndexType::BTree);
2505        let fulltext_idx =
2506            catalog.create_index("idx_fulltext", person_id, name_id, IndexType::FullText);
2507
2508        assert_eq!(catalog.index_count(), 3);
2509
2510        let indexes = catalog.indexes_for_label_property(person_id, name_id);
2511        assert_eq!(indexes.len(), 3);
2512        assert!(indexes.contains(&hash_idx));
2513        assert!(indexes.contains(&btree_idx));
2514        assert!(indexes.contains(&fulltext_idx));
2515
2516        // Verify each has the correct type
2517        assert_eq!(
2518            catalog.get_index(hash_idx).unwrap().index_type,
2519            IndexType::Hash
2520        );
2521        assert_eq!(
2522            catalog.get_index(btree_idx).unwrap().index_type,
2523            IndexType::BTree
2524        );
2525        assert_eq!(
2526            catalog.get_index(fulltext_idx).unwrap().index_type,
2527            IndexType::FullText
2528        );
2529    }
2530
2531    #[test]
2532    fn test_catalog_schema_required_property_duplicate() {
2533        let catalog = Catalog::with_schema();
2534
2535        let person_id = catalog.get_or_create_label("Person");
2536        let name_id = catalog.get_or_create_property_key("name");
2537
2538        // First should succeed
2539        assert!(catalog.add_required_property(person_id, name_id).is_ok());
2540
2541        // Duplicate should fail
2542        assert_eq!(
2543            catalog.add_required_property(person_id, name_id),
2544            Err(CatalogError::ConstraintAlreadyExists)
2545        );
2546    }
2547
2548    #[test]
2549    fn test_catalog_schema_check_without_constraints() {
2550        let catalog = Catalog::new();
2551
2552        let person_id = catalog.get_or_create_label("Person");
2553        let name_id = catalog.get_or_create_property_key("name");
2554
2555        // Without schema enabled, these should return false
2556        assert!(!catalog.is_property_unique(person_id, name_id));
2557        assert!(!catalog.is_property_required(person_id, name_id));
2558    }
2559
2560    #[test]
2561    fn test_catalog_has_schema() {
2562        // Both new() and with_schema() enable schema by default
2563        let catalog = Catalog::new();
2564        assert!(catalog.has_schema());
2565
2566        let with_schema = Catalog::with_schema();
2567        assert!(with_schema.has_schema());
2568    }
2569
2570    #[test]
2571    fn test_catalog_error_display() {
2572        assert_eq!(
2573            CatalogError::SchemaNotEnabled.to_string(),
2574            "Schema constraints are not enabled"
2575        );
2576        assert_eq!(
2577            CatalogError::ConstraintAlreadyExists.to_string(),
2578            "Constraint already exists"
2579        );
2580        assert_eq!(
2581            CatalogError::LabelNotFound("Person".to_string()).to_string(),
2582            "Label not found: Person"
2583        );
2584        assert_eq!(
2585            CatalogError::PropertyKeyNotFound("name".to_string()).to_string(),
2586            "Property key not found: name"
2587        );
2588        assert_eq!(
2589            CatalogError::EdgeTypeNotFound("KNOWS".to_string()).to_string(),
2590            "Edge type not found: KNOWS"
2591        );
2592        let idx = IndexId::new(42);
2593        assert!(CatalogError::IndexNotFound(idx).to_string().contains("42"));
2594    }
2595
2596    #[test]
2597    fn test_catalog_concurrent_label_creation() {
2598        use std::sync::Arc;
2599
2600        let catalog = Arc::new(Catalog::new());
2601        let mut handles = vec![];
2602
2603        // Spawn multiple threads trying to create the same labels
2604        for i in 0..10 {
2605            let catalog = Arc::clone(&catalog);
2606            handles.push(thread::spawn(move || {
2607                let label_name = format!("Label{}", i % 3); // Only 3 unique labels
2608                catalog.get_or_create_label(&label_name)
2609            }));
2610        }
2611
2612        let mut ids: Vec<LabelId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2613        ids.sort_by_key(|id| id.as_u32());
2614        ids.dedup();
2615
2616        // Should only have 3 unique label IDs
2617        assert_eq!(ids.len(), 3);
2618        assert_eq!(catalog.label_count(), 3);
2619    }
2620
2621    #[test]
2622    fn test_catalog_concurrent_property_key_creation() {
2623        use std::sync::Arc;
2624
2625        let catalog = Arc::new(Catalog::new());
2626        let mut handles = vec![];
2627
2628        for i in 0..10 {
2629            let catalog = Arc::clone(&catalog);
2630            handles.push(thread::spawn(move || {
2631                let key_name = format!("key{}", i % 4);
2632                catalog.get_or_create_property_key(&key_name)
2633            }));
2634        }
2635
2636        let mut ids: Vec<PropertyKeyId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2637        ids.sort_by_key(|id| id.as_u32());
2638        ids.dedup();
2639
2640        assert_eq!(ids.len(), 4);
2641        assert_eq!(catalog.property_key_count(), 4);
2642    }
2643
2644    #[test]
2645    fn test_catalog_concurrent_index_operations() {
2646        use std::sync::Arc;
2647
2648        let catalog = Arc::new(Catalog::new());
2649        let label = catalog.get_or_create_label("Node");
2650
2651        let mut handles = vec![];
2652
2653        // Create indexes concurrently
2654        for i in 0..5 {
2655            let catalog = Arc::clone(&catalog);
2656            handles.push(thread::spawn(move || {
2657                let prop = PropertyKeyId::new(i);
2658                catalog.create_index(&format!("idx_{i}"), label, prop, IndexType::Hash)
2659            }));
2660        }
2661
2662        let ids: Vec<IndexId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2663        assert_eq!(ids.len(), 5);
2664        assert_eq!(catalog.index_count(), 5);
2665    }
2666
2667    #[test]
2668    fn test_catalog_special_characters_in_names() {
2669        let catalog = Catalog::new();
2670
2671        // Test with various special characters
2672        let label1 = catalog.get_or_create_label("Label With Spaces");
2673        let label2 = catalog.get_or_create_label("Label-With-Dashes");
2674        let label3 = catalog.get_or_create_label("Label_With_Underscores");
2675        let label4 = catalog.get_or_create_label("LabelWithUnicode\u{00E9}");
2676
2677        assert_ne!(label1, label2);
2678        assert_ne!(label2, label3);
2679        assert_ne!(label3, label4);
2680
2681        assert_eq!(
2682            catalog.get_label_name(label1).as_deref(),
2683            Some("Label With Spaces")
2684        );
2685        assert_eq!(
2686            catalog.get_label_name(label4).as_deref(),
2687            Some("LabelWithUnicode\u{00E9}")
2688        );
2689    }
2690
2691    #[test]
2692    fn test_catalog_empty_names() {
2693        let catalog = Catalog::new();
2694
2695        // Empty names should be valid (edge case)
2696        let empty_label = catalog.get_or_create_label("");
2697        let empty_prop = catalog.get_or_create_property_key("");
2698        let empty_edge = catalog.get_or_create_edge_type("");
2699
2700        assert_eq!(catalog.get_label_name(empty_label).as_deref(), Some(""));
2701        assert_eq!(
2702            catalog.get_property_key_name(empty_prop).as_deref(),
2703            Some("")
2704        );
2705        assert_eq!(catalog.get_edge_type_name(empty_edge).as_deref(), Some(""));
2706
2707        // Calling again should return same ID
2708        assert_eq!(catalog.get_or_create_label(""), empty_label);
2709    }
2710
2711    #[test]
2712    fn test_catalog_large_number_of_entries() {
2713        let catalog = Catalog::new();
2714
2715        // Create many labels
2716        for i in 0..1000 {
2717            catalog.get_or_create_label(&format!("Label{}", i));
2718        }
2719
2720        assert_eq!(catalog.label_count(), 1000);
2721
2722        // Verify we can retrieve them all
2723        let all = catalog.all_labels();
2724        assert_eq!(all.len(), 1000);
2725
2726        // Verify a specific one
2727        let id = catalog.get_label_id("Label500").unwrap();
2728        assert_eq!(catalog.get_label_name(id).as_deref(), Some("Label500"));
2729    }
2730
2731    #[test]
2732    fn test_index_definition_debug() {
2733        let def = IndexDefinition {
2734            id: IndexId::new(1),
2735            name: "test_index".to_string(),
2736            label: LabelId::new(2),
2737            property_key: PropertyKeyId::new(3),
2738            index_type: IndexType::Hash,
2739        };
2740
2741        // Should be able to debug print
2742        let debug_str = format!("{:?}", def);
2743        assert!(debug_str.contains("IndexDefinition"));
2744        assert!(debug_str.contains("Hash"));
2745    }
2746
2747    #[test]
2748    fn test_index_type_equality() {
2749        assert_eq!(IndexType::Hash, IndexType::Hash);
2750        assert_ne!(IndexType::Hash, IndexType::BTree);
2751        assert_ne!(IndexType::BTree, IndexType::FullText);
2752
2753        // Clone
2754        let t = IndexType::Hash;
2755        let t2 = t;
2756        assert_eq!(t, t2);
2757    }
2758
2759    #[test]
2760    fn test_catalog_error_equality() {
2761        assert_eq!(
2762            CatalogError::SchemaNotEnabled,
2763            CatalogError::SchemaNotEnabled
2764        );
2765        assert_eq!(
2766            CatalogError::ConstraintAlreadyExists,
2767            CatalogError::ConstraintAlreadyExists
2768        );
2769        assert_eq!(
2770            CatalogError::LabelNotFound("X".to_string()),
2771            CatalogError::LabelNotFound("X".to_string())
2772        );
2773        assert_ne!(
2774            CatalogError::LabelNotFound("X".to_string()),
2775            CatalogError::LabelNotFound("Y".to_string())
2776        );
2777    }
2778}