Skip to main content

grafeo_engine/catalog/
mod.rs

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