1mod 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
25pub struct Catalog {
30 labels: LabelCatalog,
32 property_keys: PropertyCatalog,
34 edge_types: EdgeTypeCatalog,
36 indexes: IndexCatalog,
38 schema: Option<SchemaCatalog>,
40}
41
42impl Catalog {
43 #[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 #[must_use]
59 pub fn with_schema() -> Self {
60 Self::new()
61 }
62
63 pub fn get_or_create_label(&self, name: &str) -> LabelId {
67 self.labels.get_or_create(name)
68 }
69
70 #[must_use]
72 pub fn get_label_id(&self, name: &str) -> Option<LabelId> {
73 self.labels.get_id(name)
74 }
75
76 #[must_use]
78 pub fn get_label_name(&self, id: LabelId) -> Option<Arc<str>> {
79 self.labels.get_name(id)
80 }
81
82 #[must_use]
84 pub fn label_count(&self) -> usize {
85 self.labels.count()
86 }
87
88 #[must_use]
90 pub fn all_labels(&self) -> Vec<Arc<str>> {
91 self.labels.all_names()
92 }
93
94 pub fn get_or_create_property_key(&self, name: &str) -> PropertyKeyId {
98 self.property_keys.get_or_create(name)
99 }
100
101 #[must_use]
103 pub fn get_property_key_id(&self, name: &str) -> Option<PropertyKeyId> {
104 self.property_keys.get_id(name)
105 }
106
107 #[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 #[must_use]
115 pub fn property_key_count(&self) -> usize {
116 self.property_keys.count()
117 }
118
119 #[must_use]
121 pub fn all_property_keys(&self) -> Vec<Arc<str>> {
122 self.property_keys.all_names()
123 }
124
125 pub fn get_or_create_edge_type(&self, name: &str) -> EdgeTypeId {
129 self.edge_types.get_or_create(name)
130 }
131
132 #[must_use]
134 pub fn get_edge_type_id(&self, name: &str) -> Option<EdgeTypeId> {
135 self.edge_types.get_id(name)
136 }
137
138 #[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 #[must_use]
146 pub fn edge_type_count(&self) -> usize {
147 self.edge_types.count()
148 }
149
150 #[must_use]
152 pub fn all_edge_types(&self) -> Vec<Arc<str>> {
153 self.edge_types.all_names()
154 }
155
156 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 pub fn drop_index(&self, id: IndexId) -> bool {
171 self.indexes.drop(id)
172 }
173
174 #[must_use]
176 pub fn find_index_by_name(&self, name: &str) -> Option<IndexId> {
177 self.indexes.find_by_name(name)
178 }
179
180 #[must_use]
182 pub fn get_index(&self, id: IndexId) -> Option<IndexDefinition> {
183 self.indexes.get(id)
184 }
185
186 #[must_use]
188 pub fn indexes_for_label(&self, label: LabelId) -> Vec<IndexId> {
189 self.indexes.for_label(label)
190 }
191
192 #[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 #[must_use]
204 pub fn all_indexes(&self) -> Vec<IndexDefinition> {
205 self.indexes.all()
206 }
207
208 #[must_use]
210 pub fn index_count(&self) -> usize {
211 self.indexes.count()
212 }
213
214 #[must_use]
218 pub fn has_schema(&self) -> bool {
219 self.schema.is_some()
220 }
221
222 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 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 #[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 #[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 #[must_use]
280 pub fn schema(&self) -> Option<&SchemaCatalog> {
281 self.schema.as_ref()
282 }
283
284 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 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 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 #[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 #[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 #[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 #[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 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 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 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 #[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 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 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 #[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 #[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 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 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 #[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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
704 self.schema.as_ref()?.get_procedure(name)
705 }
706
707 #[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 #[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 #[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 #[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 #[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
759struct 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 if let Some(id) = self.name_to_id.get(name) {
786 return *id;
787 }
788
789 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
819struct 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 if let Some(id) = self.name_to_id.get(name) {
842 return *id;
843 }
844
845 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
875struct 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 if let Some(id) = self.name_to_id.get(name) {
898 return *id;
899 }
900
901 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
935#[non_exhaustive]
936pub enum IndexType {
937 Hash,
939 BTree,
941 FullText,
943}
944
945#[derive(Debug, Clone)]
947pub struct IndexDefinition {
948 pub id: IndexId,
950 pub name: String,
952 pub label: LabelId,
954 pub property_key: PropertyKeyId,
956 pub index_type: IndexType,
958}
959
960struct 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 if let Some(ids) = label_indexes.get_mut(&definition.label) {
1019 ids.retain(|&i| i != id);
1020 }
1021 if let Some(ids) =
1023 label_property_indexes.get_mut(&(definition.label, definition.property_key))
1024 {
1025 ids.retain(|&i| i != id);
1026 }
1027 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#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1072pub enum PropertyDataType {
1073 String,
1075 Int64,
1077 Float64,
1079 Bool,
1081 Date,
1083 Time,
1085 Timestamp,
1087 Duration,
1089 List,
1091 ListTyped(Box<PropertyDataType>),
1093 Map,
1095 Bytes,
1097 Node,
1099 Edge,
1101 Any,
1103}
1104
1105impl PropertyDataType {
1106 #[must_use]
1108 pub fn from_type_name(name: &str) -> Self {
1109 let upper = name.to_uppercase();
1110 if let Some(inner) = upper
1112 .strip_prefix("LIST<")
1113 .and_then(|s| s.strip_suffix('>'))
1114 {
1115 return Self::ListTyped(Box::new(Self::from_type_name(inner)));
1116 }
1117 match upper.as_str() {
1118 "STRING" | "VARCHAR" | "TEXT" => Self::String,
1119 "INT" | "INT64" | "INTEGER" | "BIGINT" => Self::Int64,
1120 "FLOAT" | "FLOAT64" | "DOUBLE" | "REAL" => Self::Float64,
1121 "BOOL" | "BOOLEAN" => Self::Bool,
1122 "DATE" => Self::Date,
1123 "TIME" => Self::Time,
1124 "TIMESTAMP" | "DATETIME" => Self::Timestamp,
1125 "DURATION" | "INTERVAL" => Self::Duration,
1126 "LIST" | "ARRAY" => Self::List,
1127 "MAP" | "RECORD" => Self::Map,
1128 "BYTES" | "BINARY" | "BLOB" => Self::Bytes,
1129 "NODE" => Self::Node,
1130 "EDGE" | "RELATIONSHIP" => Self::Edge,
1131 _ => Self::Any,
1132 }
1133 }
1134
1135 #[must_use]
1137 pub fn matches(&self, value: &Value) -> bool {
1138 match (self, value) {
1139 (Self::Any, _) | (_, Value::Null) => true,
1140 (Self::String, Value::String(_)) => true,
1141 (Self::Int64, Value::Int64(_)) => true,
1142 (Self::Float64, Value::Float64(_)) => true,
1143 (Self::Bool, Value::Bool(_)) => true,
1144 (Self::Date, Value::Date(_)) => true,
1145 (Self::Time, Value::Time(_)) => true,
1146 (Self::Timestamp, Value::Timestamp(_)) => true,
1147 (Self::Duration, Value::Duration(_)) => true,
1148 (Self::List, Value::List(_)) => true,
1149 (Self::ListTyped(elem_type), Value::List(items)) => {
1150 items.iter().all(|item| elem_type.matches(item))
1151 }
1152 (Self::Bytes, Value::Bytes(_)) => true,
1153 (Self::Node | Self::Edge, Value::Map(_)) => true,
1156 _ => false,
1157 }
1158 }
1159}
1160
1161impl std::fmt::Display for PropertyDataType {
1162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1163 match self {
1164 Self::String => write!(f, "STRING"),
1165 Self::Int64 => write!(f, "INT64"),
1166 Self::Float64 => write!(f, "FLOAT64"),
1167 Self::Bool => write!(f, "BOOLEAN"),
1168 Self::Date => write!(f, "DATE"),
1169 Self::Time => write!(f, "TIME"),
1170 Self::Timestamp => write!(f, "TIMESTAMP"),
1171 Self::Duration => write!(f, "DURATION"),
1172 Self::List => write!(f, "LIST"),
1173 Self::ListTyped(elem) => write!(f, "LIST<{elem}>"),
1174 Self::Map => write!(f, "MAP"),
1175 Self::Bytes => write!(f, "BYTES"),
1176 Self::Node => write!(f, "NODE"),
1177 Self::Edge => write!(f, "EDGE"),
1178 Self::Any => write!(f, "ANY"),
1179 }
1180 }
1181}
1182
1183#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1185pub struct TypedProperty {
1186 pub name: String,
1188 pub data_type: PropertyDataType,
1190 pub nullable: bool,
1192 pub default_value: Option<Value>,
1194}
1195
1196#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1198pub enum TypeConstraint {
1199 PrimaryKey(Vec<String>),
1201 Unique(Vec<String>),
1203 NotNull(String),
1205 Check {
1207 name: Option<String>,
1209 expression: String,
1211 },
1212}
1213
1214#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1216pub struct NodeTypeDefinition {
1217 pub name: String,
1219 pub properties: Vec<TypedProperty>,
1221 pub constraints: Vec<TypeConstraint>,
1223 pub parent_types: Vec<String>,
1225}
1226
1227#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1229pub struct EdgeTypeDefinition {
1230 pub name: String,
1232 pub properties: Vec<TypedProperty>,
1234 pub constraints: Vec<TypeConstraint>,
1236 pub source_node_types: Vec<String>,
1238 pub target_node_types: Vec<String>,
1240}
1241
1242#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1244pub struct GraphTypeDefinition {
1245 pub name: String,
1247 pub allowed_node_types: Vec<String>,
1249 pub allowed_edge_types: Vec<String>,
1251 pub open: bool,
1253}
1254
1255#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1257pub struct ProcedureDefinition {
1258 pub name: String,
1260 pub params: Vec<(String, String)>,
1262 pub returns: Vec<(String, String)>,
1264 pub body: String,
1266}
1267
1268pub struct SchemaCatalog {
1272 unique_constraints: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1274 required_properties: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1276 node_types: RwLock<HashMap<String, NodeTypeDefinition>>,
1278 edge_types: RwLock<HashMap<String, EdgeTypeDefinition>>,
1280 graph_types: RwLock<HashMap<String, GraphTypeDefinition>>,
1282 schemas: RwLock<Vec<String>>,
1284 graph_type_bindings: RwLock<HashMap<String, String>>,
1286 procedures: RwLock<HashMap<String, ProcedureDefinition>>,
1288}
1289
1290impl SchemaCatalog {
1291 fn new() -> Self {
1292 Self {
1293 unique_constraints: RwLock::new(HashSet::new()),
1294 required_properties: RwLock::new(HashSet::new()),
1295 node_types: RwLock::new(HashMap::new()),
1296 edge_types: RwLock::new(HashMap::new()),
1297 graph_types: RwLock::new(HashMap::new()),
1298 schemas: RwLock::new(Vec::new()),
1299 graph_type_bindings: RwLock::new(HashMap::new()),
1300 procedures: RwLock::new(HashMap::new()),
1301 }
1302 }
1303
1304 pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
1312 let mut types = self.node_types.write();
1313 if types.contains_key(&def.name) {
1314 return Err(CatalogError::TypeAlreadyExists(def.name));
1315 }
1316 types.insert(def.name.clone(), def);
1317 Ok(())
1318 }
1319
1320 pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
1322 self.node_types.write().insert(def.name.clone(), def);
1323 }
1324
1325 pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
1331 let mut types = self.node_types.write();
1332 if types.remove(name).is_none() {
1333 return Err(CatalogError::TypeNotFound(name.to_string()));
1334 }
1335 Ok(())
1336 }
1337
1338 #[must_use]
1340 pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1341 self.node_types.read().get(name).cloned()
1342 }
1343
1344 #[must_use]
1350 pub fn resolved_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1351 let types = self.node_types.read();
1352 let base = types.get(name)?;
1353 if base.parent_types.is_empty() {
1354 return Some(base.clone());
1355 }
1356 let mut visited = HashSet::new();
1357 visited.insert(name.to_string());
1358 let mut all_properties = Vec::new();
1359 let mut all_constraints = Vec::new();
1360 Self::collect_inherited(
1361 &types,
1362 name,
1363 &mut visited,
1364 &mut all_properties,
1365 &mut all_constraints,
1366 );
1367 Some(NodeTypeDefinition {
1368 name: base.name.clone(),
1369 properties: all_properties,
1370 constraints: all_constraints,
1371 parent_types: base.parent_types.clone(),
1372 })
1373 }
1374
1375 fn collect_inherited(
1377 types: &HashMap<String, NodeTypeDefinition>,
1378 name: &str,
1379 visited: &mut HashSet<String>,
1380 properties: &mut Vec<TypedProperty>,
1381 constraints: &mut Vec<TypeConstraint>,
1382 ) {
1383 let Some(def) = types.get(name) else { return };
1384 for parent in &def.parent_types {
1386 if visited.insert(parent.clone()) {
1387 Self::collect_inherited(types, parent, visited, properties, constraints);
1388 }
1389 }
1390 for prop in &def.properties {
1392 if let Some(pos) = properties.iter().position(|p| p.name == prop.name) {
1393 properties[pos] = prop.clone();
1394 } else {
1395 properties.push(prop.clone());
1396 }
1397 }
1398 constraints.extend(def.constraints.iter().cloned());
1400 }
1401
1402 #[must_use]
1404 pub fn all_node_types(&self) -> Vec<String> {
1405 self.node_types.read().keys().cloned().collect()
1406 }
1407
1408 #[must_use]
1410 pub fn all_node_type_defs(&self) -> Vec<NodeTypeDefinition> {
1411 self.node_types.read().values().cloned().collect()
1412 }
1413
1414 pub fn register_edge_type(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
1422 let mut types = self.edge_types.write();
1423 if types.contains_key(&def.name) {
1424 return Err(CatalogError::TypeAlreadyExists(def.name));
1425 }
1426 types.insert(def.name.clone(), def);
1427 Ok(())
1428 }
1429
1430 pub fn register_or_replace_edge_type(&self, def: EdgeTypeDefinition) {
1432 self.edge_types.write().insert(def.name.clone(), def);
1433 }
1434
1435 pub fn drop_edge_type(&self, name: &str) -> Result<(), CatalogError> {
1441 let mut types = self.edge_types.write();
1442 if types.remove(name).is_none() {
1443 return Err(CatalogError::TypeNotFound(name.to_string()));
1444 }
1445 Ok(())
1446 }
1447
1448 #[must_use]
1450 pub fn get_edge_type(&self, name: &str) -> Option<EdgeTypeDefinition> {
1451 self.edge_types.read().get(name).cloned()
1452 }
1453
1454 #[must_use]
1456 pub fn all_edge_types(&self) -> Vec<String> {
1457 self.edge_types.read().keys().cloned().collect()
1458 }
1459
1460 #[must_use]
1462 pub fn all_edge_type_defs(&self) -> Vec<EdgeTypeDefinition> {
1463 self.edge_types.read().values().cloned().collect()
1464 }
1465
1466 pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
1474 let mut types = self.graph_types.write();
1475 if types.contains_key(&def.name) {
1476 return Err(CatalogError::TypeAlreadyExists(def.name));
1477 }
1478 types.insert(def.name.clone(), def);
1479 Ok(())
1480 }
1481
1482 pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
1488 let mut types = self.graph_types.write();
1489 if types.remove(name).is_none() {
1490 return Err(CatalogError::TypeNotFound(name.to_string()));
1491 }
1492 Ok(())
1493 }
1494
1495 #[must_use]
1497 pub fn get_graph_type(&self, name: &str) -> Option<GraphTypeDefinition> {
1498 self.graph_types.read().get(name).cloned()
1499 }
1500
1501 #[must_use]
1503 pub fn all_graph_types(&self) -> Vec<String> {
1504 self.graph_types.read().keys().cloned().collect()
1505 }
1506
1507 #[must_use]
1509 pub fn all_graph_type_defs(&self) -> Vec<GraphTypeDefinition> {
1510 self.graph_types.read().values().cloned().collect()
1511 }
1512
1513 pub fn register_schema(&self, name: String) -> Result<(), CatalogError> {
1521 let mut schemas = self.schemas.write();
1522 if schemas.contains(&name) {
1523 return Err(CatalogError::SchemaAlreadyExists(name));
1524 }
1525 schemas.push(name);
1526 Ok(())
1527 }
1528
1529 pub fn drop_schema(&self, name: &str) -> Result<(), CatalogError> {
1535 let mut schemas = self.schemas.write();
1536 if let Some(pos) = schemas.iter().position(|s| s == name) {
1537 schemas.remove(pos);
1538 Ok(())
1539 } else {
1540 Err(CatalogError::SchemaNotFound(name.to_string()))
1541 }
1542 }
1543
1544 #[must_use]
1546 pub fn schema_exists(&self, name: &str) -> bool {
1547 self.schemas
1548 .read()
1549 .iter()
1550 .any(|s| s.eq_ignore_ascii_case(name))
1551 }
1552
1553 #[must_use]
1555 pub fn schema_names(&self) -> Vec<String> {
1556 self.schemas.read().clone()
1557 }
1558
1559 pub fn add_constraint_to_type(
1567 &self,
1568 label: &str,
1569 constraint: TypeConstraint,
1570 ) -> Result<(), CatalogError> {
1571 let mut types = self.node_types.write();
1572 if let Some(def) = types.get_mut(label) {
1573 def.constraints.push(constraint);
1574 } else {
1575 types.insert(
1577 label.to_string(),
1578 NodeTypeDefinition {
1579 name: label.to_string(),
1580 properties: Vec::new(),
1581 constraints: vec![constraint],
1582 parent_types: Vec::new(),
1583 },
1584 );
1585 }
1586 Ok(())
1587 }
1588
1589 pub fn alter_node_type_add_property(
1596 &self,
1597 type_name: &str,
1598 property: TypedProperty,
1599 ) -> Result<(), CatalogError> {
1600 let mut types = self.node_types.write();
1601 let def = types
1602 .get_mut(type_name)
1603 .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1604 if def.properties.iter().any(|p| p.name == property.name) {
1605 return Err(CatalogError::TypeAlreadyExists(format!(
1606 "property {} on {}",
1607 property.name, type_name
1608 )));
1609 }
1610 def.properties.push(property);
1611 Ok(())
1612 }
1613
1614 pub fn alter_node_type_drop_property(
1620 &self,
1621 type_name: &str,
1622 property_name: &str,
1623 ) -> Result<(), CatalogError> {
1624 let mut types = self.node_types.write();
1625 let def = types
1626 .get_mut(type_name)
1627 .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1628 let len_before = def.properties.len();
1629 def.properties.retain(|p| p.name != property_name);
1630 if def.properties.len() == len_before {
1631 return Err(CatalogError::TypeNotFound(format!(
1632 "property {} on {}",
1633 property_name, type_name
1634 )));
1635 }
1636 Ok(())
1637 }
1638
1639 pub fn alter_edge_type_add_property(
1646 &self,
1647 type_name: &str,
1648 property: TypedProperty,
1649 ) -> Result<(), CatalogError> {
1650 let mut types = self.edge_types.write();
1651 let def = types
1652 .get_mut(type_name)
1653 .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1654 if def.properties.iter().any(|p| p.name == property.name) {
1655 return Err(CatalogError::TypeAlreadyExists(format!(
1656 "property {} on {}",
1657 property.name, type_name
1658 )));
1659 }
1660 def.properties.push(property);
1661 Ok(())
1662 }
1663
1664 pub fn alter_edge_type_drop_property(
1670 &self,
1671 type_name: &str,
1672 property_name: &str,
1673 ) -> Result<(), CatalogError> {
1674 let mut types = self.edge_types.write();
1675 let def = types
1676 .get_mut(type_name)
1677 .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1678 let len_before = def.properties.len();
1679 def.properties.retain(|p| p.name != property_name);
1680 if def.properties.len() == len_before {
1681 return Err(CatalogError::TypeNotFound(format!(
1682 "property {} on {}",
1683 property_name, type_name
1684 )));
1685 }
1686 Ok(())
1687 }
1688
1689 pub fn alter_graph_type_add_node_type(
1695 &self,
1696 graph_type_name: &str,
1697 node_type: String,
1698 ) -> Result<(), CatalogError> {
1699 let mut types = self.graph_types.write();
1700 let def = types
1701 .get_mut(graph_type_name)
1702 .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1703 if !def.allowed_node_types.contains(&node_type) {
1704 def.allowed_node_types.push(node_type);
1705 }
1706 Ok(())
1707 }
1708
1709 pub fn alter_graph_type_drop_node_type(
1715 &self,
1716 graph_type_name: &str,
1717 node_type: &str,
1718 ) -> Result<(), CatalogError> {
1719 let mut types = self.graph_types.write();
1720 let def = types
1721 .get_mut(graph_type_name)
1722 .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1723 def.allowed_node_types.retain(|t| t != node_type);
1724 Ok(())
1725 }
1726
1727 pub fn alter_graph_type_add_edge_type(
1733 &self,
1734 graph_type_name: &str,
1735 edge_type: String,
1736 ) -> Result<(), CatalogError> {
1737 let mut types = self.graph_types.write();
1738 let def = types
1739 .get_mut(graph_type_name)
1740 .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1741 if !def.allowed_edge_types.contains(&edge_type) {
1742 def.allowed_edge_types.push(edge_type);
1743 }
1744 Ok(())
1745 }
1746
1747 pub fn alter_graph_type_drop_edge_type(
1753 &self,
1754 graph_type_name: &str,
1755 edge_type: &str,
1756 ) -> Result<(), CatalogError> {
1757 let mut types = self.graph_types.write();
1758 let def = types
1759 .get_mut(graph_type_name)
1760 .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1761 def.allowed_edge_types.retain(|t| t != edge_type);
1762 Ok(())
1763 }
1764
1765 pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
1773 let mut procs = self.procedures.write();
1774 if procs.contains_key(&def.name) {
1775 return Err(CatalogError::TypeAlreadyExists(def.name.clone()));
1776 }
1777 procs.insert(def.name.clone(), def);
1778 Ok(())
1779 }
1780
1781 pub fn replace_procedure(&self, def: ProcedureDefinition) {
1783 self.procedures.write().insert(def.name.clone(), def);
1784 }
1785
1786 pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
1792 let mut procs = self.procedures.write();
1793 if procs.remove(name).is_none() {
1794 return Err(CatalogError::TypeNotFound(name.to_string()));
1795 }
1796 Ok(())
1797 }
1798
1799 pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
1801 self.procedures.read().get(name).cloned()
1802 }
1803
1804 #[must_use]
1806 pub fn all_procedure_defs(&self) -> Vec<ProcedureDefinition> {
1807 self.procedures.read().values().cloned().collect()
1808 }
1809
1810 #[must_use]
1812 pub fn all_graph_type_bindings(&self) -> Vec<(String, String)> {
1813 self.graph_type_bindings
1814 .read()
1815 .iter()
1816 .map(|(k, v)| (k.clone(), v.clone()))
1817 .collect()
1818 }
1819
1820 fn add_unique_constraint(
1821 &self,
1822 label: LabelId,
1823 property_key: PropertyKeyId,
1824 ) -> Result<(), CatalogError> {
1825 let mut constraints = self.unique_constraints.write();
1826 let key = (label, property_key);
1827 if !constraints.insert(key) {
1828 return Err(CatalogError::ConstraintAlreadyExists);
1829 }
1830 Ok(())
1831 }
1832
1833 fn add_required_property(
1834 &self,
1835 label: LabelId,
1836 property_key: PropertyKeyId,
1837 ) -> Result<(), CatalogError> {
1838 let mut required = self.required_properties.write();
1839 let key = (label, property_key);
1840 if !required.insert(key) {
1841 return Err(CatalogError::ConstraintAlreadyExists);
1842 }
1843 Ok(())
1844 }
1845
1846 fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1847 self.required_properties
1848 .read()
1849 .contains(&(label, property_key))
1850 }
1851
1852 fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1853 self.unique_constraints
1854 .read()
1855 .contains(&(label, property_key))
1856 }
1857}
1858
1859#[derive(Debug, Clone, PartialEq, Eq)]
1863pub enum CatalogError {
1864 SchemaNotEnabled,
1866 ConstraintAlreadyExists,
1868 LabelNotFound(String),
1870 PropertyKeyNotFound(String),
1872 EdgeTypeNotFound(String),
1874 IndexNotFound(IndexId),
1876 TypeAlreadyExists(String),
1878 TypeNotFound(String),
1880 SchemaAlreadyExists(String),
1882 SchemaNotFound(String),
1884}
1885
1886impl std::fmt::Display for CatalogError {
1887 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1888 match self {
1889 Self::SchemaNotEnabled => write!(f, "Schema constraints are not enabled"),
1890 Self::ConstraintAlreadyExists => write!(f, "Constraint already exists"),
1891 Self::LabelNotFound(name) => write!(f, "Label not found: {name}"),
1892 Self::PropertyKeyNotFound(name) => write!(f, "Property key not found: {name}"),
1893 Self::EdgeTypeNotFound(name) => write!(f, "Edge type not found: {name}"),
1894 Self::IndexNotFound(id) => write!(f, "Index not found: {id}"),
1895 Self::TypeAlreadyExists(name) => write!(f, "Type already exists: {name}"),
1896 Self::TypeNotFound(name) => write!(f, "Type not found: {name}"),
1897 Self::SchemaAlreadyExists(name) => write!(f, "Schema already exists: {name}"),
1898 Self::SchemaNotFound(name) => write!(f, "Schema not found: {name}"),
1899 }
1900 }
1901}
1902
1903impl std::error::Error for CatalogError {}
1904
1905use grafeo_core::execution::operators::ConstraintValidator;
1908use grafeo_core::execution::operators::OperatorError;
1909
1910pub struct CatalogConstraintValidator {
1915 catalog: Arc<Catalog>,
1916 graph_name: Option<String>,
1918 store: Option<Arc<dyn grafeo_core::graph::GraphStore>>,
1920}
1921
1922impl CatalogConstraintValidator {
1923 pub fn new(catalog: Arc<Catalog>) -> Self {
1925 Self {
1926 catalog,
1927 graph_name: None,
1928 store: None,
1929 }
1930 }
1931
1932 pub fn with_graph_name(mut self, name: String) -> Self {
1934 self.graph_name = Some(name);
1935 self
1936 }
1937
1938 pub fn with_store(mut self, store: Arc<dyn grafeo_core::graph::GraphStore>) -> Self {
1940 self.store = Some(store);
1941 self
1942 }
1943}
1944
1945impl ConstraintValidator for CatalogConstraintValidator {
1946 fn validate_node_property(
1947 &self,
1948 labels: &[String],
1949 key: &str,
1950 value: &Value,
1951 ) -> Result<(), OperatorError> {
1952 for label in labels {
1953 if let Some(type_def) = self.catalog.resolved_node_type(label)
1954 && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1955 {
1956 if !typed_prop.nullable && *value == Value::Null {
1958 return Err(OperatorError::ConstraintViolation(format!(
1959 "property '{key}' on :{label} is NOT NULL, cannot set to null"
1960 )));
1961 }
1962 if *value != Value::Null && !typed_prop.data_type.matches(value) {
1964 return Err(OperatorError::ConstraintViolation(format!(
1965 "property '{key}' on :{label} expects {:?}, got {:?}",
1966 typed_prop.data_type, value
1967 )));
1968 }
1969 }
1970 }
1971 Ok(())
1972 }
1973
1974 fn validate_node_complete(
1975 &self,
1976 labels: &[String],
1977 properties: &[(String, Value)],
1978 ) -> Result<(), OperatorError> {
1979 let prop_names: std::collections::HashSet<&str> =
1980 properties.iter().map(|(n, _)| n.as_str()).collect();
1981
1982 for label in labels {
1983 if let Some(type_def) = self.catalog.resolved_node_type(label) {
1984 for typed_prop in &type_def.properties {
1986 if !typed_prop.nullable
1987 && typed_prop.default_value.is_none()
1988 && !prop_names.contains(typed_prop.name.as_str())
1989 {
1990 return Err(OperatorError::ConstraintViolation(format!(
1991 "missing required property '{}' on :{label}",
1992 typed_prop.name
1993 )));
1994 }
1995 }
1996 for constraint in &type_def.constraints {
1998 match constraint {
1999 TypeConstraint::NotNull(prop_name) => {
2000 if !prop_names.contains(prop_name.as_str()) {
2001 return Err(OperatorError::ConstraintViolation(format!(
2002 "missing required property '{prop_name}' on :{label} (NOT NULL constraint)"
2003 )));
2004 }
2005 }
2006 TypeConstraint::PrimaryKey(key_props) => {
2007 for pk in key_props {
2008 if !prop_names.contains(pk.as_str()) {
2009 return Err(OperatorError::ConstraintViolation(format!(
2010 "missing primary key property '{pk}' on :{label}"
2011 )));
2012 }
2013 }
2014 }
2015 TypeConstraint::Check { name, expression } => {
2016 match check_eval::evaluate_check(expression, properties) {
2017 Ok(true) => {}
2018 Ok(false) => {
2019 let constraint_name = name.as_deref().unwrap_or("unnamed");
2020 return Err(OperatorError::ConstraintViolation(format!(
2021 "CHECK constraint '{constraint_name}' violated on :{label}"
2022 )));
2023 }
2024 Err(err) => {
2025 return Err(OperatorError::ConstraintViolation(format!(
2026 "CHECK constraint evaluation error: {err}"
2027 )));
2028 }
2029 }
2030 }
2031 TypeConstraint::Unique(_) => {}
2032 }
2033 }
2034 }
2035 }
2036 Ok(())
2037 }
2038
2039 fn check_unique_node_property(
2040 &self,
2041 labels: &[String],
2042 key: &str,
2043 value: &Value,
2044 ) -> Result<(), OperatorError> {
2045 if *value == Value::Null {
2047 return Ok(());
2048 }
2049 for label in labels {
2050 if let Some(type_def) = self.catalog.resolved_node_type(label) {
2051 for constraint in &type_def.constraints {
2052 let is_unique = match constraint {
2053 TypeConstraint::Unique(props) => props.iter().any(|p| p == key),
2054 TypeConstraint::PrimaryKey(props) => props.iter().any(|p| p == key),
2055 _ => false,
2056 };
2057 if is_unique && let Some(ref store) = self.store {
2058 let existing = store.find_nodes_by_property(key, value);
2059 for node_id in existing {
2060 if let Some(node) = store.get_node(node_id) {
2061 let has_label = node.labels.iter().any(|l| l.as_str() == label);
2062 if has_label {
2063 return Err(OperatorError::ConstraintViolation(format!(
2064 "UNIQUE constraint violation: property '{key}' \
2065 with value {value:?} already exists on :{label}"
2066 )));
2067 }
2068 }
2069 }
2070 }
2071 }
2072 }
2073 }
2074 Ok(())
2075 }
2076
2077 fn validate_edge_property(
2078 &self,
2079 edge_type: &str,
2080 key: &str,
2081 value: &Value,
2082 ) -> Result<(), OperatorError> {
2083 if let Some(type_def) = self.catalog.get_edge_type_def(edge_type)
2084 && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
2085 {
2086 if !typed_prop.nullable && *value == Value::Null {
2088 return Err(OperatorError::ConstraintViolation(format!(
2089 "property '{key}' on :{edge_type} is NOT NULL, cannot set to null"
2090 )));
2091 }
2092 if *value != Value::Null && !typed_prop.data_type.matches(value) {
2094 return Err(OperatorError::ConstraintViolation(format!(
2095 "property '{key}' on :{edge_type} expects {:?}, got {:?}",
2096 typed_prop.data_type, value
2097 )));
2098 }
2099 }
2100 Ok(())
2101 }
2102
2103 fn validate_edge_complete(
2104 &self,
2105 edge_type: &str,
2106 properties: &[(String, Value)],
2107 ) -> Result<(), OperatorError> {
2108 if let Some(type_def) = self.catalog.get_edge_type_def(edge_type) {
2109 let prop_names: std::collections::HashSet<&str> =
2110 properties.iter().map(|(n, _)| n.as_str()).collect();
2111
2112 for typed_prop in &type_def.properties {
2113 if !typed_prop.nullable
2114 && typed_prop.default_value.is_none()
2115 && !prop_names.contains(typed_prop.name.as_str())
2116 {
2117 return Err(OperatorError::ConstraintViolation(format!(
2118 "missing required property '{}' on :{edge_type}",
2119 typed_prop.name
2120 )));
2121 }
2122 }
2123
2124 for constraint in &type_def.constraints {
2125 if let TypeConstraint::Check { name, expression } = constraint {
2126 match check_eval::evaluate_check(expression, properties) {
2127 Ok(true) => {}
2128 Ok(false) => {
2129 let constraint_name = name.as_deref().unwrap_or("unnamed");
2130 return Err(OperatorError::ConstraintViolation(format!(
2131 "CHECK constraint '{constraint_name}' violated on :{edge_type}"
2132 )));
2133 }
2134 Err(err) => {
2135 return Err(OperatorError::ConstraintViolation(format!(
2136 "CHECK constraint evaluation error: {err}"
2137 )));
2138 }
2139 }
2140 }
2141 }
2142 }
2143 Ok(())
2144 }
2145
2146 fn validate_node_labels_allowed(&self, labels: &[String]) -> Result<(), OperatorError> {
2147 let Some(ref graph_name) = self.graph_name else {
2148 return Ok(());
2149 };
2150 let Some(type_name) = self.catalog.get_graph_type_binding(graph_name) else {
2151 return Ok(());
2152 };
2153 let Some(gt) = self
2154 .catalog
2155 .schema()
2156 .and_then(|s| s.get_graph_type(&type_name))
2157 else {
2158 return Ok(());
2159 };
2160 if !gt.open && !gt.allowed_node_types.is_empty() {
2161 let allowed = labels
2162 .iter()
2163 .any(|l| gt.allowed_node_types.iter().any(|a| a == l));
2164 if !allowed {
2165 return Err(OperatorError::ConstraintViolation(format!(
2166 "node labels {labels:?} are not allowed by graph type '{}'",
2167 gt.name
2168 )));
2169 }
2170 }
2171 Ok(())
2172 }
2173
2174 fn validate_edge_type_allowed(&self, edge_type: &str) -> Result<(), OperatorError> {
2175 let Some(ref graph_name) = self.graph_name else {
2176 return Ok(());
2177 };
2178 let Some(type_name) = self.catalog.get_graph_type_binding(graph_name) else {
2179 return Ok(());
2180 };
2181 let Some(gt) = self
2182 .catalog
2183 .schema()
2184 .and_then(|s| s.get_graph_type(&type_name))
2185 else {
2186 return Ok(());
2187 };
2188 if !gt.open && !gt.allowed_edge_types.is_empty() {
2189 let allowed = gt.allowed_edge_types.iter().any(|a| a == edge_type);
2190 if !allowed {
2191 return Err(OperatorError::ConstraintViolation(format!(
2192 "edge type '{edge_type}' is not allowed by graph type '{}'",
2193 gt.name
2194 )));
2195 }
2196 }
2197 Ok(())
2198 }
2199
2200 fn validate_edge_endpoints(
2201 &self,
2202 edge_type: &str,
2203 source_labels: &[String],
2204 target_labels: &[String],
2205 ) -> Result<(), OperatorError> {
2206 let Some(type_def) = self.catalog.get_edge_type_def(edge_type) else {
2207 return Ok(());
2208 };
2209 if !type_def.source_node_types.is_empty() {
2210 let source_ok = source_labels
2211 .iter()
2212 .any(|l| type_def.source_node_types.iter().any(|s| s == l));
2213 if !source_ok {
2214 return Err(OperatorError::ConstraintViolation(format!(
2215 "source node labels {source_labels:?} are not allowed for edge type '{edge_type}', \
2216 expected one of {:?}",
2217 type_def.source_node_types
2218 )));
2219 }
2220 }
2221 if !type_def.target_node_types.is_empty() {
2222 let target_ok = target_labels
2223 .iter()
2224 .any(|l| type_def.target_node_types.iter().any(|t| t == l));
2225 if !target_ok {
2226 return Err(OperatorError::ConstraintViolation(format!(
2227 "target node labels {target_labels:?} are not allowed for edge type '{edge_type}', \
2228 expected one of {:?}",
2229 type_def.target_node_types
2230 )));
2231 }
2232 }
2233 Ok(())
2234 }
2235
2236 fn inject_defaults(&self, labels: &[String], properties: &mut Vec<(String, Value)>) {
2237 for label in labels {
2238 if let Some(type_def) = self.catalog.resolved_node_type(label) {
2239 for typed_prop in &type_def.properties {
2240 if let Some(ref default) = typed_prop.default_value {
2241 let already_set = properties.iter().any(|(n, _)| n == &typed_prop.name);
2242 if !already_set {
2243 properties.push((typed_prop.name.clone(), default.clone()));
2244 }
2245 }
2246 }
2247 }
2248 }
2249 }
2250}
2251
2252#[cfg(test)]
2253mod tests {
2254 use super::*;
2255 use std::thread;
2256
2257 #[test]
2258 fn test_catalog_labels() {
2259 let catalog = Catalog::new();
2260
2261 let person_id = catalog.get_or_create_label("Person");
2263 let company_id = catalog.get_or_create_label("Company");
2264
2265 assert_ne!(person_id, company_id);
2267
2268 assert_eq!(catalog.get_or_create_label("Person"), person_id);
2270
2271 assert_eq!(catalog.get_label_id("Person"), Some(person_id));
2273 assert_eq!(catalog.get_label_id("Company"), Some(company_id));
2274 assert_eq!(catalog.get_label_id("Unknown"), None);
2275
2276 assert_eq!(catalog.get_label_name(person_id).as_deref(), Some("Person"));
2278 assert_eq!(
2279 catalog.get_label_name(company_id).as_deref(),
2280 Some("Company")
2281 );
2282
2283 assert_eq!(catalog.label_count(), 2);
2285 }
2286
2287 #[test]
2288 fn test_catalog_property_keys() {
2289 let catalog = Catalog::new();
2290
2291 let name_id = catalog.get_or_create_property_key("name");
2292 let age_id = catalog.get_or_create_property_key("age");
2293
2294 assert_ne!(name_id, age_id);
2295 assert_eq!(catalog.get_or_create_property_key("name"), name_id);
2296 assert_eq!(catalog.get_property_key_id("name"), Some(name_id));
2297 assert_eq!(
2298 catalog.get_property_key_name(name_id).as_deref(),
2299 Some("name")
2300 );
2301 assert_eq!(catalog.property_key_count(), 2);
2302 }
2303
2304 #[test]
2305 fn test_catalog_edge_types() {
2306 let catalog = Catalog::new();
2307
2308 let knows_id = catalog.get_or_create_edge_type("KNOWS");
2309 let works_at_id = catalog.get_or_create_edge_type("WORKS_AT");
2310
2311 assert_ne!(knows_id, works_at_id);
2312 assert_eq!(catalog.get_or_create_edge_type("KNOWS"), knows_id);
2313 assert_eq!(catalog.get_edge_type_id("KNOWS"), Some(knows_id));
2314 assert_eq!(
2315 catalog.get_edge_type_name(knows_id).as_deref(),
2316 Some("KNOWS")
2317 );
2318 assert_eq!(catalog.edge_type_count(), 2);
2319 }
2320
2321 #[test]
2322 fn test_catalog_indexes() {
2323 let catalog = Catalog::new();
2324
2325 let person_id = catalog.get_or_create_label("Person");
2326 let name_id = catalog.get_or_create_property_key("name");
2327 let age_id = catalog.get_or_create_property_key("age");
2328
2329 let idx1 = catalog.create_index("idx_person_name", person_id, name_id, IndexType::Hash);
2331 let idx2 = catalog.create_index("idx_person_age", person_id, age_id, IndexType::BTree);
2332
2333 assert_ne!(idx1, idx2);
2334 assert_eq!(catalog.index_count(), 2);
2335
2336 let label_indexes = catalog.indexes_for_label(person_id);
2338 assert_eq!(label_indexes.len(), 2);
2339 assert!(label_indexes.contains(&idx1));
2340 assert!(label_indexes.contains(&idx2));
2341
2342 let name_indexes = catalog.indexes_for_label_property(person_id, name_id);
2344 assert_eq!(name_indexes.len(), 1);
2345 assert_eq!(name_indexes[0], idx1);
2346
2347 let def = catalog.get_index(idx1).unwrap();
2349 assert_eq!(def.label, person_id);
2350 assert_eq!(def.property_key, name_id);
2351 assert_eq!(def.index_type, IndexType::Hash);
2352
2353 assert!(catalog.drop_index(idx1));
2355 assert_eq!(catalog.index_count(), 1);
2356 assert!(catalog.get_index(idx1).is_none());
2357 assert_eq!(catalog.indexes_for_label(person_id).len(), 1);
2358 }
2359
2360 #[test]
2361 fn test_catalog_schema_constraints() {
2362 let catalog = Catalog::with_schema();
2363
2364 let person_id = catalog.get_or_create_label("Person");
2365 let email_id = catalog.get_or_create_property_key("email");
2366 let name_id = catalog.get_or_create_property_key("name");
2367
2368 assert!(catalog.add_unique_constraint(person_id, email_id).is_ok());
2370 assert!(catalog.add_required_property(person_id, name_id).is_ok());
2371
2372 assert!(catalog.is_property_unique(person_id, email_id));
2374 assert!(!catalog.is_property_unique(person_id, name_id));
2375 assert!(catalog.is_property_required(person_id, name_id));
2376 assert!(!catalog.is_property_required(person_id, email_id));
2377
2378 assert_eq!(
2380 catalog.add_unique_constraint(person_id, email_id),
2381 Err(CatalogError::ConstraintAlreadyExists)
2382 );
2383 }
2384
2385 #[test]
2386 fn test_catalog_schema_always_enabled() {
2387 let catalog = Catalog::new();
2389 assert!(catalog.has_schema());
2390
2391 let person_id = catalog.get_or_create_label("Person");
2392 let email_id = catalog.get_or_create_property_key("email");
2393
2394 assert_eq!(catalog.add_unique_constraint(person_id, email_id), Ok(()));
2396 }
2397
2398 #[test]
2401 fn test_catalog_default() {
2402 let catalog = Catalog::default();
2403 assert!(catalog.has_schema());
2404 assert_eq!(catalog.label_count(), 0);
2405 assert_eq!(catalog.property_key_count(), 0);
2406 assert_eq!(catalog.edge_type_count(), 0);
2407 assert_eq!(catalog.index_count(), 0);
2408 }
2409
2410 #[test]
2411 fn test_catalog_all_labels() {
2412 let catalog = Catalog::new();
2413
2414 catalog.get_or_create_label("Person");
2415 catalog.get_or_create_label("Company");
2416 catalog.get_or_create_label("Product");
2417
2418 let all = catalog.all_labels();
2419 assert_eq!(all.len(), 3);
2420 assert!(all.iter().any(|l| l.as_ref() == "Person"));
2421 assert!(all.iter().any(|l| l.as_ref() == "Company"));
2422 assert!(all.iter().any(|l| l.as_ref() == "Product"));
2423 }
2424
2425 #[test]
2426 fn test_catalog_all_property_keys() {
2427 let catalog = Catalog::new();
2428
2429 catalog.get_or_create_property_key("name");
2430 catalog.get_or_create_property_key("age");
2431 catalog.get_or_create_property_key("email");
2432
2433 let all = catalog.all_property_keys();
2434 assert_eq!(all.len(), 3);
2435 assert!(all.iter().any(|k| k.as_ref() == "name"));
2436 assert!(all.iter().any(|k| k.as_ref() == "age"));
2437 assert!(all.iter().any(|k| k.as_ref() == "email"));
2438 }
2439
2440 #[test]
2441 fn test_catalog_all_edge_types() {
2442 let catalog = Catalog::new();
2443
2444 catalog.get_or_create_edge_type("KNOWS");
2445 catalog.get_or_create_edge_type("WORKS_AT");
2446 catalog.get_or_create_edge_type("LIVES_IN");
2447
2448 let all = catalog.all_edge_types();
2449 assert_eq!(all.len(), 3);
2450 assert!(all.iter().any(|t| t.as_ref() == "KNOWS"));
2451 assert!(all.iter().any(|t| t.as_ref() == "WORKS_AT"));
2452 assert!(all.iter().any(|t| t.as_ref() == "LIVES_IN"));
2453 }
2454
2455 #[test]
2456 fn test_catalog_invalid_id_lookup() {
2457 let catalog = Catalog::new();
2458
2459 let _ = catalog.get_or_create_label("Person");
2461
2462 let invalid_label = LabelId::new(999);
2464 let invalid_property = PropertyKeyId::new(999);
2465 let invalid_edge_type = EdgeTypeId::new(999);
2466 let invalid_index = IndexId::new(999);
2467
2468 assert!(catalog.get_label_name(invalid_label).is_none());
2469 assert!(catalog.get_property_key_name(invalid_property).is_none());
2470 assert!(catalog.get_edge_type_name(invalid_edge_type).is_none());
2471 assert!(catalog.get_index(invalid_index).is_none());
2472 }
2473
2474 #[test]
2475 fn test_catalog_drop_nonexistent_index() {
2476 let catalog = Catalog::new();
2477 let invalid_index = IndexId::new(999);
2478 assert!(!catalog.drop_index(invalid_index));
2479 }
2480
2481 #[test]
2482 fn test_catalog_indexes_for_nonexistent_label() {
2483 let catalog = Catalog::new();
2484 let invalid_label = LabelId::new(999);
2485 let invalid_property = PropertyKeyId::new(999);
2486
2487 assert!(catalog.indexes_for_label(invalid_label).is_empty());
2488 assert!(
2489 catalog
2490 .indexes_for_label_property(invalid_label, invalid_property)
2491 .is_empty()
2492 );
2493 }
2494
2495 #[test]
2496 fn test_catalog_multiple_indexes_same_property() {
2497 let catalog = Catalog::new();
2498
2499 let person_id = catalog.get_or_create_label("Person");
2500 let name_id = catalog.get_or_create_property_key("name");
2501
2502 let hash_idx = catalog.create_index("idx_hash", person_id, name_id, IndexType::Hash);
2504 let btree_idx = catalog.create_index("idx_btree", person_id, name_id, IndexType::BTree);
2505 let fulltext_idx =
2506 catalog.create_index("idx_fulltext", person_id, name_id, IndexType::FullText);
2507
2508 assert_eq!(catalog.index_count(), 3);
2509
2510 let indexes = catalog.indexes_for_label_property(person_id, name_id);
2511 assert_eq!(indexes.len(), 3);
2512 assert!(indexes.contains(&hash_idx));
2513 assert!(indexes.contains(&btree_idx));
2514 assert!(indexes.contains(&fulltext_idx));
2515
2516 assert_eq!(
2518 catalog.get_index(hash_idx).unwrap().index_type,
2519 IndexType::Hash
2520 );
2521 assert_eq!(
2522 catalog.get_index(btree_idx).unwrap().index_type,
2523 IndexType::BTree
2524 );
2525 assert_eq!(
2526 catalog.get_index(fulltext_idx).unwrap().index_type,
2527 IndexType::FullText
2528 );
2529 }
2530
2531 #[test]
2532 fn test_catalog_schema_required_property_duplicate() {
2533 let catalog = Catalog::with_schema();
2534
2535 let person_id = catalog.get_or_create_label("Person");
2536 let name_id = catalog.get_or_create_property_key("name");
2537
2538 assert!(catalog.add_required_property(person_id, name_id).is_ok());
2540
2541 assert_eq!(
2543 catalog.add_required_property(person_id, name_id),
2544 Err(CatalogError::ConstraintAlreadyExists)
2545 );
2546 }
2547
2548 #[test]
2549 fn test_catalog_schema_check_without_constraints() {
2550 let catalog = Catalog::new();
2551
2552 let person_id = catalog.get_or_create_label("Person");
2553 let name_id = catalog.get_or_create_property_key("name");
2554
2555 assert!(!catalog.is_property_unique(person_id, name_id));
2557 assert!(!catalog.is_property_required(person_id, name_id));
2558 }
2559
2560 #[test]
2561 fn test_catalog_has_schema() {
2562 let catalog = Catalog::new();
2564 assert!(catalog.has_schema());
2565
2566 let with_schema = Catalog::with_schema();
2567 assert!(with_schema.has_schema());
2568 }
2569
2570 #[test]
2571 fn test_catalog_error_display() {
2572 assert_eq!(
2573 CatalogError::SchemaNotEnabled.to_string(),
2574 "Schema constraints are not enabled"
2575 );
2576 assert_eq!(
2577 CatalogError::ConstraintAlreadyExists.to_string(),
2578 "Constraint already exists"
2579 );
2580 assert_eq!(
2581 CatalogError::LabelNotFound("Person".to_string()).to_string(),
2582 "Label not found: Person"
2583 );
2584 assert_eq!(
2585 CatalogError::PropertyKeyNotFound("name".to_string()).to_string(),
2586 "Property key not found: name"
2587 );
2588 assert_eq!(
2589 CatalogError::EdgeTypeNotFound("KNOWS".to_string()).to_string(),
2590 "Edge type not found: KNOWS"
2591 );
2592 let idx = IndexId::new(42);
2593 assert!(CatalogError::IndexNotFound(idx).to_string().contains("42"));
2594 }
2595
2596 #[test]
2597 fn test_catalog_concurrent_label_creation() {
2598 use std::sync::Arc;
2599
2600 let catalog = Arc::new(Catalog::new());
2601 let mut handles = vec![];
2602
2603 for i in 0..10 {
2605 let catalog = Arc::clone(&catalog);
2606 handles.push(thread::spawn(move || {
2607 let label_name = format!("Label{}", i % 3); catalog.get_or_create_label(&label_name)
2609 }));
2610 }
2611
2612 let mut ids: Vec<LabelId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2613 ids.sort_by_key(|id| id.as_u32());
2614 ids.dedup();
2615
2616 assert_eq!(ids.len(), 3);
2618 assert_eq!(catalog.label_count(), 3);
2619 }
2620
2621 #[test]
2622 fn test_catalog_concurrent_property_key_creation() {
2623 use std::sync::Arc;
2624
2625 let catalog = Arc::new(Catalog::new());
2626 let mut handles = vec![];
2627
2628 for i in 0..10 {
2629 let catalog = Arc::clone(&catalog);
2630 handles.push(thread::spawn(move || {
2631 let key_name = format!("key{}", i % 4);
2632 catalog.get_or_create_property_key(&key_name)
2633 }));
2634 }
2635
2636 let mut ids: Vec<PropertyKeyId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2637 ids.sort_by_key(|id| id.as_u32());
2638 ids.dedup();
2639
2640 assert_eq!(ids.len(), 4);
2641 assert_eq!(catalog.property_key_count(), 4);
2642 }
2643
2644 #[test]
2645 fn test_catalog_concurrent_index_operations() {
2646 use std::sync::Arc;
2647
2648 let catalog = Arc::new(Catalog::new());
2649 let label = catalog.get_or_create_label("Node");
2650
2651 let mut handles = vec![];
2652
2653 for i in 0..5 {
2655 let catalog = Arc::clone(&catalog);
2656 handles.push(thread::spawn(move || {
2657 let prop = PropertyKeyId::new(i);
2658 catalog.create_index(&format!("idx_{i}"), label, prop, IndexType::Hash)
2659 }));
2660 }
2661
2662 let ids: Vec<IndexId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2663 assert_eq!(ids.len(), 5);
2664 assert_eq!(catalog.index_count(), 5);
2665 }
2666
2667 #[test]
2668 fn test_catalog_special_characters_in_names() {
2669 let catalog = Catalog::new();
2670
2671 let label1 = catalog.get_or_create_label("Label With Spaces");
2673 let label2 = catalog.get_or_create_label("Label-With-Dashes");
2674 let label3 = catalog.get_or_create_label("Label_With_Underscores");
2675 let label4 = catalog.get_or_create_label("LabelWithUnicode\u{00E9}");
2676
2677 assert_ne!(label1, label2);
2678 assert_ne!(label2, label3);
2679 assert_ne!(label3, label4);
2680
2681 assert_eq!(
2682 catalog.get_label_name(label1).as_deref(),
2683 Some("Label With Spaces")
2684 );
2685 assert_eq!(
2686 catalog.get_label_name(label4).as_deref(),
2687 Some("LabelWithUnicode\u{00E9}")
2688 );
2689 }
2690
2691 #[test]
2692 fn test_catalog_empty_names() {
2693 let catalog = Catalog::new();
2694
2695 let empty_label = catalog.get_or_create_label("");
2697 let empty_prop = catalog.get_or_create_property_key("");
2698 let empty_edge = catalog.get_or_create_edge_type("");
2699
2700 assert_eq!(catalog.get_label_name(empty_label).as_deref(), Some(""));
2701 assert_eq!(
2702 catalog.get_property_key_name(empty_prop).as_deref(),
2703 Some("")
2704 );
2705 assert_eq!(catalog.get_edge_type_name(empty_edge).as_deref(), Some(""));
2706
2707 assert_eq!(catalog.get_or_create_label(""), empty_label);
2709 }
2710
2711 #[test]
2712 fn test_catalog_large_number_of_entries() {
2713 let catalog = Catalog::new();
2714
2715 for i in 0..1000 {
2717 catalog.get_or_create_label(&format!("Label{}", i));
2718 }
2719
2720 assert_eq!(catalog.label_count(), 1000);
2721
2722 let all = catalog.all_labels();
2724 assert_eq!(all.len(), 1000);
2725
2726 let id = catalog.get_label_id("Label500").unwrap();
2728 assert_eq!(catalog.get_label_name(id).as_deref(), Some("Label500"));
2729 }
2730
2731 #[test]
2732 fn test_index_definition_debug() {
2733 let def = IndexDefinition {
2734 id: IndexId::new(1),
2735 name: "test_index".to_string(),
2736 label: LabelId::new(2),
2737 property_key: PropertyKeyId::new(3),
2738 index_type: IndexType::Hash,
2739 };
2740
2741 let debug_str = format!("{:?}", def);
2743 assert!(debug_str.contains("IndexDefinition"));
2744 assert!(debug_str.contains("Hash"));
2745 }
2746
2747 #[test]
2748 fn test_index_type_equality() {
2749 assert_eq!(IndexType::Hash, IndexType::Hash);
2750 assert_ne!(IndexType::Hash, IndexType::BTree);
2751 assert_ne!(IndexType::BTree, IndexType::FullText);
2752
2753 let t = IndexType::Hash;
2755 let t2 = t;
2756 assert_eq!(t, t2);
2757 }
2758
2759 #[test]
2760 fn test_catalog_error_equality() {
2761 assert_eq!(
2762 CatalogError::SchemaNotEnabled,
2763 CatalogError::SchemaNotEnabled
2764 );
2765 assert_eq!(
2766 CatalogError::ConstraintAlreadyExists,
2767 CatalogError::ConstraintAlreadyExists
2768 );
2769 assert_eq!(
2770 CatalogError::LabelNotFound("X".to_string()),
2771 CatalogError::LabelNotFound("X".to_string())
2772 );
2773 assert_ne!(
2774 CatalogError::LabelNotFound("X".to_string()),
2775 CatalogError::LabelNotFound("Y".to_string())
2776 );
2777 }
2778}