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)]
1072#[non_exhaustive]
1073pub enum PropertyDataType {
1074 String,
1076 Int64,
1078 Float64,
1080 Bool,
1082 Date,
1084 Time,
1086 Timestamp,
1088 Duration,
1090 List,
1092 ListTyped(Box<PropertyDataType>),
1094 Map,
1096 Bytes,
1098 Node,
1100 Edge,
1102 Any,
1104}
1105
1106impl PropertyDataType {
1107 #[must_use]
1109 pub fn from_type_name(name: &str) -> Self {
1110 let upper = name.to_uppercase();
1111 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 #[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 (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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1186pub struct TypedProperty {
1187 pub name: String,
1189 pub data_type: PropertyDataType,
1191 pub nullable: bool,
1193 pub default_value: Option<Value>,
1195}
1196
1197#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1199#[non_exhaustive]
1200pub enum TypeConstraint {
1201 PrimaryKey(Vec<String>),
1203 Unique(Vec<String>),
1205 NotNull(String),
1207 Check {
1209 name: Option<String>,
1211 expression: String,
1213 },
1214}
1215
1216#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1218pub struct NodeTypeDefinition {
1219 pub name: String,
1221 pub properties: Vec<TypedProperty>,
1223 pub constraints: Vec<TypeConstraint>,
1225 pub parent_types: Vec<String>,
1227}
1228
1229#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1231pub struct EdgeTypeDefinition {
1232 pub name: String,
1234 pub properties: Vec<TypedProperty>,
1236 pub constraints: Vec<TypeConstraint>,
1238 pub source_node_types: Vec<String>,
1240 pub target_node_types: Vec<String>,
1242}
1243
1244#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1246pub struct GraphTypeDefinition {
1247 pub name: String,
1249 pub allowed_node_types: Vec<String>,
1251 pub allowed_edge_types: Vec<String>,
1253 pub open: bool,
1255}
1256
1257#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1259pub struct ProcedureDefinition {
1260 pub name: String,
1262 pub params: Vec<(String, String)>,
1264 pub returns: Vec<(String, String)>,
1266 pub body: String,
1268}
1269
1270pub struct SchemaCatalog {
1274 unique_constraints: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1276 required_properties: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1278 node_types: RwLock<HashMap<String, NodeTypeDefinition>>,
1280 edge_types: RwLock<HashMap<String, EdgeTypeDefinition>>,
1282 graph_types: RwLock<HashMap<String, GraphTypeDefinition>>,
1284 schemas: RwLock<Vec<String>>,
1286 graph_type_bindings: RwLock<HashMap<String, String>>,
1288 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 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 pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
1324 self.node_types.write().insert(def.name.clone(), def);
1325 }
1326
1327 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 #[must_use]
1342 pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1343 self.node_types.read().get(name).cloned()
1344 }
1345
1346 #[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 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 for parent in &def.parent_types {
1388 if visited.insert(parent.clone()) {
1389 Self::collect_inherited(types, parent, visited, properties, constraints);
1390 }
1391 }
1392 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 constraints.extend(def.constraints.iter().cloned());
1402 }
1403
1404 #[must_use]
1406 pub fn all_node_types(&self) -> Vec<String> {
1407 self.node_types.read().keys().cloned().collect()
1408 }
1409
1410 #[must_use]
1412 pub fn all_node_type_defs(&self) -> Vec<NodeTypeDefinition> {
1413 self.node_types.read().values().cloned().collect()
1414 }
1415
1416 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 pub fn register_or_replace_edge_type(&self, def: EdgeTypeDefinition) {
1434 self.edge_types.write().insert(def.name.clone(), def);
1435 }
1436
1437 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 #[must_use]
1452 pub fn get_edge_type(&self, name: &str) -> Option<EdgeTypeDefinition> {
1453 self.edge_types.read().get(name).cloned()
1454 }
1455
1456 #[must_use]
1458 pub fn all_edge_types(&self) -> Vec<String> {
1459 self.edge_types.read().keys().cloned().collect()
1460 }
1461
1462 #[must_use]
1464 pub fn all_edge_type_defs(&self) -> Vec<EdgeTypeDefinition> {
1465 self.edge_types.read().values().cloned().collect()
1466 }
1467
1468 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 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 #[must_use]
1499 pub fn get_graph_type(&self, name: &str) -> Option<GraphTypeDefinition> {
1500 self.graph_types.read().get(name).cloned()
1501 }
1502
1503 #[must_use]
1505 pub fn all_graph_types(&self) -> Vec<String> {
1506 self.graph_types.read().keys().cloned().collect()
1507 }
1508
1509 #[must_use]
1511 pub fn all_graph_type_defs(&self) -> Vec<GraphTypeDefinition> {
1512 self.graph_types.read().values().cloned().collect()
1513 }
1514
1515 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 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 #[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 #[must_use]
1557 pub fn schema_names(&self) -> Vec<String> {
1558 self.schemas.read().clone()
1559 }
1560
1561 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 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 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 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 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 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 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 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 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 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 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 pub fn replace_procedure(&self, def: ProcedureDefinition) {
1785 self.procedures.write().insert(def.name.clone(), def);
1786 }
1787
1788 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 pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
1803 self.procedures.read().get(name).cloned()
1804 }
1805
1806 #[must_use]
1808 pub fn all_procedure_defs(&self) -> Vec<ProcedureDefinition> {
1809 self.procedures.read().values().cloned().collect()
1810 }
1811
1812 #[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#[derive(Debug, Clone, PartialEq, Eq)]
1865#[non_exhaustive]
1866pub enum CatalogError {
1867 SchemaNotEnabled,
1869 ConstraintAlreadyExists,
1871 LabelNotFound(String),
1873 PropertyKeyNotFound(String),
1875 EdgeTypeNotFound(String),
1877 IndexNotFound(IndexId),
1879 TypeAlreadyExists(String),
1881 TypeNotFound(String),
1883 SchemaAlreadyExists(String),
1885 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
1908use grafeo_core::execution::operators::ConstraintValidator;
1911use grafeo_core::execution::operators::OperatorError;
1912
1913pub struct CatalogConstraintValidator {
1918 catalog: Arc<Catalog>,
1919 graph_name: Option<String>,
1921 store: Option<Arc<dyn grafeo_core::graph::GraphStore>>,
1923 max_property_size: Option<usize>,
1925}
1926
1927impl CatalogConstraintValidator {
1928 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 pub fn with_graph_name(mut self, name: String) -> Self {
1940 self.graph_name = Some(name);
1941 self
1942 }
1943
1944 pub fn with_store(mut self, store: Arc<dyn grafeo_core::graph::GraphStore>) -> Self {
1946 self.store = Some(store);
1947 self
1948 }
1949
1950 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 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 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 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 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 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 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 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 let person_id = catalog.get_or_create_label("Person");
2293 let company_id = catalog.get_or_create_label("Company");
2294
2295 assert_ne!(person_id, company_id);
2297
2298 assert_eq!(catalog.get_or_create_label("Person"), person_id);
2300
2301 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 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(catalog.add_unique_constraint(person_id, email_id), Ok(()));
2426 }
2427
2428 #[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 let _ = catalog.get_or_create_label("Person");
2491
2492 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 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 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 assert!(catalog.add_required_property(person_id, name_id).is_ok());
2570
2571 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 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 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 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); 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 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 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 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 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 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 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 let all = catalog.all_labels();
2754 assert_eq!(all.len(), 1000);
2755
2756 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 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 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}