1use std::collections::HashMap;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicU32, Ordering};
13
14use parking_lot::RwLock;
15
16use grafeo_common::types::{EdgeTypeId, IndexId, LabelId, PropertyKeyId};
17
18pub struct Catalog {
26 labels: LabelCatalog,
28 property_keys: PropertyCatalog,
30 edge_types: EdgeTypeCatalog,
32 indexes: IndexCatalog,
34 schema: Option<SchemaCatalog>,
36}
37
38impl Catalog {
39 #[must_use]
41 pub fn new() -> Self {
42 Self {
43 labels: LabelCatalog::new(),
44 property_keys: PropertyCatalog::new(),
45 edge_types: EdgeTypeCatalog::new(),
46 indexes: IndexCatalog::new(),
47 schema: None,
48 }
49 }
50
51 #[must_use]
53 pub fn with_schema() -> Self {
54 Self {
55 labels: LabelCatalog::new(),
56 property_keys: PropertyCatalog::new(),
57 edge_types: EdgeTypeCatalog::new(),
58 indexes: IndexCatalog::new(),
59 schema: Some(SchemaCatalog::new()),
60 }
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 label: LabelId,
162 property_key: PropertyKeyId,
163 index_type: IndexType,
164 ) -> IndexId {
165 self.indexes.create(label, property_key, index_type)
166 }
167
168 pub fn drop_index(&self, id: IndexId) -> bool {
170 self.indexes.drop(id)
171 }
172
173 #[must_use]
175 pub fn get_index(&self, id: IndexId) -> Option<IndexDefinition> {
176 self.indexes.get(id)
177 }
178
179 #[must_use]
181 pub fn indexes_for_label(&self, label: LabelId) -> Vec<IndexId> {
182 self.indexes.for_label(label)
183 }
184
185 #[must_use]
187 pub fn indexes_for_label_property(
188 &self,
189 label: LabelId,
190 property_key: PropertyKeyId,
191 ) -> Vec<IndexId> {
192 self.indexes.for_label_property(label, property_key)
193 }
194
195 #[must_use]
197 pub fn index_count(&self) -> usize {
198 self.indexes.count()
199 }
200
201 #[must_use]
205 pub fn has_schema(&self) -> bool {
206 self.schema.is_some()
207 }
208
209 pub fn add_unique_constraint(
213 &self,
214 label: LabelId,
215 property_key: PropertyKeyId,
216 ) -> Result<(), CatalogError> {
217 match &self.schema {
218 Some(schema) => schema.add_unique_constraint(label, property_key),
219 None => Err(CatalogError::SchemaNotEnabled),
220 }
221 }
222
223 pub fn add_required_property(
227 &self,
228 label: LabelId,
229 property_key: PropertyKeyId,
230 ) -> Result<(), CatalogError> {
231 match &self.schema {
232 Some(schema) => schema.add_required_property(label, property_key),
233 None => Err(CatalogError::SchemaNotEnabled),
234 }
235 }
236
237 #[must_use]
239 pub fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
240 self.schema
241 .as_ref()
242 .is_some_and(|s| s.is_property_required(label, property_key))
243 }
244
245 #[must_use]
247 pub fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
248 self.schema
249 .as_ref()
250 .is_some_and(|s| s.is_property_unique(label, property_key))
251 }
252}
253
254impl Default for Catalog {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260struct LabelCatalog {
264 name_to_id: RwLock<HashMap<Arc<str>, LabelId>>,
265 id_to_name: RwLock<Vec<Arc<str>>>,
266 next_id: AtomicU32,
267}
268
269impl LabelCatalog {
270 fn new() -> Self {
271 Self {
272 name_to_id: RwLock::new(HashMap::new()),
273 id_to_name: RwLock::new(Vec::new()),
274 next_id: AtomicU32::new(0),
275 }
276 }
277
278 fn get_or_create(&self, name: &str) -> LabelId {
279 {
281 let name_to_id = self.name_to_id.read();
282 if let Some(&id) = name_to_id.get(name) {
283 return id;
284 }
285 }
286
287 let mut name_to_id = self.name_to_id.write();
289 let mut id_to_name = self.id_to_name.write();
290
291 if let Some(&id) = name_to_id.get(name) {
293 return id;
294 }
295
296 let id = LabelId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
297 let name: Arc<str> = name.into();
298 name_to_id.insert(Arc::clone(&name), id);
299 id_to_name.push(name);
300 id
301 }
302
303 fn get_id(&self, name: &str) -> Option<LabelId> {
304 self.name_to_id.read().get(name).copied()
305 }
306
307 fn get_name(&self, id: LabelId) -> Option<Arc<str>> {
308 self.id_to_name.read().get(id.as_u32() as usize).cloned()
309 }
310
311 fn count(&self) -> usize {
312 self.id_to_name.read().len()
313 }
314
315 fn all_names(&self) -> Vec<Arc<str>> {
316 self.id_to_name.read().clone()
317 }
318}
319
320struct PropertyCatalog {
324 name_to_id: RwLock<HashMap<Arc<str>, PropertyKeyId>>,
325 id_to_name: RwLock<Vec<Arc<str>>>,
326 next_id: AtomicU32,
327}
328
329impl PropertyCatalog {
330 fn new() -> Self {
331 Self {
332 name_to_id: RwLock::new(HashMap::new()),
333 id_to_name: RwLock::new(Vec::new()),
334 next_id: AtomicU32::new(0),
335 }
336 }
337
338 fn get_or_create(&self, name: &str) -> PropertyKeyId {
339 {
341 let name_to_id = self.name_to_id.read();
342 if let Some(&id) = name_to_id.get(name) {
343 return id;
344 }
345 }
346
347 let mut name_to_id = self.name_to_id.write();
349 let mut id_to_name = self.id_to_name.write();
350
351 if let Some(&id) = name_to_id.get(name) {
353 return id;
354 }
355
356 let id = PropertyKeyId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
357 let name: Arc<str> = name.into();
358 name_to_id.insert(Arc::clone(&name), id);
359 id_to_name.push(name);
360 id
361 }
362
363 fn get_id(&self, name: &str) -> Option<PropertyKeyId> {
364 self.name_to_id.read().get(name).copied()
365 }
366
367 fn get_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
368 self.id_to_name.read().get(id.as_u32() as usize).cloned()
369 }
370
371 fn count(&self) -> usize {
372 self.id_to_name.read().len()
373 }
374
375 fn all_names(&self) -> Vec<Arc<str>> {
376 self.id_to_name.read().clone()
377 }
378}
379
380struct EdgeTypeCatalog {
384 name_to_id: RwLock<HashMap<Arc<str>, EdgeTypeId>>,
385 id_to_name: RwLock<Vec<Arc<str>>>,
386 next_id: AtomicU32,
387}
388
389impl EdgeTypeCatalog {
390 fn new() -> Self {
391 Self {
392 name_to_id: RwLock::new(HashMap::new()),
393 id_to_name: RwLock::new(Vec::new()),
394 next_id: AtomicU32::new(0),
395 }
396 }
397
398 fn get_or_create(&self, name: &str) -> EdgeTypeId {
399 {
401 let name_to_id = self.name_to_id.read();
402 if let Some(&id) = name_to_id.get(name) {
403 return id;
404 }
405 }
406
407 let mut name_to_id = self.name_to_id.write();
409 let mut id_to_name = self.id_to_name.write();
410
411 if let Some(&id) = name_to_id.get(name) {
413 return id;
414 }
415
416 let id = EdgeTypeId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
417 let name: Arc<str> = name.into();
418 name_to_id.insert(Arc::clone(&name), id);
419 id_to_name.push(name);
420 id
421 }
422
423 fn get_id(&self, name: &str) -> Option<EdgeTypeId> {
424 self.name_to_id.read().get(name).copied()
425 }
426
427 fn get_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
428 self.id_to_name.read().get(id.as_u32() as usize).cloned()
429 }
430
431 fn count(&self) -> usize {
432 self.id_to_name.read().len()
433 }
434
435 fn all_names(&self) -> Vec<Arc<str>> {
436 self.id_to_name.read().clone()
437 }
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444pub enum IndexType {
445 Hash,
447 BTree,
449 FullText,
451}
452
453#[derive(Debug, Clone)]
455pub struct IndexDefinition {
456 pub id: IndexId,
458 pub label: LabelId,
460 pub property_key: PropertyKeyId,
462 pub index_type: IndexType,
464}
465
466struct IndexCatalog {
468 indexes: RwLock<HashMap<IndexId, IndexDefinition>>,
469 label_indexes: RwLock<HashMap<LabelId, Vec<IndexId>>>,
470 label_property_indexes: RwLock<HashMap<(LabelId, PropertyKeyId), Vec<IndexId>>>,
471 next_id: AtomicU32,
472}
473
474impl IndexCatalog {
475 fn new() -> Self {
476 Self {
477 indexes: RwLock::new(HashMap::new()),
478 label_indexes: RwLock::new(HashMap::new()),
479 label_property_indexes: RwLock::new(HashMap::new()),
480 next_id: AtomicU32::new(0),
481 }
482 }
483
484 fn create(
485 &self,
486 label: LabelId,
487 property_key: PropertyKeyId,
488 index_type: IndexType,
489 ) -> IndexId {
490 let id = IndexId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
491 let definition = IndexDefinition {
492 id,
493 label,
494 property_key,
495 index_type,
496 };
497
498 let mut indexes = self.indexes.write();
499 let mut label_indexes = self.label_indexes.write();
500 let mut label_property_indexes = self.label_property_indexes.write();
501
502 indexes.insert(id, definition);
503 label_indexes.entry(label).or_default().push(id);
504 label_property_indexes
505 .entry((label, property_key))
506 .or_default()
507 .push(id);
508
509 id
510 }
511
512 fn drop(&self, id: IndexId) -> bool {
513 let mut indexes = self.indexes.write();
514 let mut label_indexes = self.label_indexes.write();
515 let mut label_property_indexes = self.label_property_indexes.write();
516
517 if let Some(definition) = indexes.remove(&id) {
518 if let Some(ids) = label_indexes.get_mut(&definition.label) {
520 ids.retain(|&i| i != id);
521 }
522 if let Some(ids) =
524 label_property_indexes.get_mut(&(definition.label, definition.property_key))
525 {
526 ids.retain(|&i| i != id);
527 }
528 true
529 } else {
530 false
531 }
532 }
533
534 fn get(&self, id: IndexId) -> Option<IndexDefinition> {
535 self.indexes.read().get(&id).cloned()
536 }
537
538 fn for_label(&self, label: LabelId) -> Vec<IndexId> {
539 self.label_indexes
540 .read()
541 .get(&label)
542 .cloned()
543 .unwrap_or_default()
544 }
545
546 fn for_label_property(&self, label: LabelId, property_key: PropertyKeyId) -> Vec<IndexId> {
547 self.label_property_indexes
548 .read()
549 .get(&(label, property_key))
550 .cloned()
551 .unwrap_or_default()
552 }
553
554 fn count(&self) -> usize {
555 self.indexes.read().len()
556 }
557}
558
559struct SchemaCatalog {
563 unique_constraints: RwLock<HashMap<(LabelId, PropertyKeyId), ()>>,
565 required_properties: RwLock<HashMap<(LabelId, PropertyKeyId), ()>>,
567}
568
569impl SchemaCatalog {
570 fn new() -> Self {
571 Self {
572 unique_constraints: RwLock::new(HashMap::new()),
573 required_properties: RwLock::new(HashMap::new()),
574 }
575 }
576
577 fn add_unique_constraint(
578 &self,
579 label: LabelId,
580 property_key: PropertyKeyId,
581 ) -> Result<(), CatalogError> {
582 let mut constraints = self.unique_constraints.write();
583 let key = (label, property_key);
584 if constraints.contains_key(&key) {
585 return Err(CatalogError::ConstraintAlreadyExists);
586 }
587 constraints.insert(key, ());
588 Ok(())
589 }
590
591 fn add_required_property(
592 &self,
593 label: LabelId,
594 property_key: PropertyKeyId,
595 ) -> Result<(), CatalogError> {
596 let mut required = self.required_properties.write();
597 let key = (label, property_key);
598 if required.contains_key(&key) {
599 return Err(CatalogError::ConstraintAlreadyExists);
600 }
601 required.insert(key, ());
602 Ok(())
603 }
604
605 fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
606 self.required_properties
607 .read()
608 .contains_key(&(label, property_key))
609 }
610
611 fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
612 self.unique_constraints
613 .read()
614 .contains_key(&(label, property_key))
615 }
616}
617
618#[derive(Debug, Clone, PartialEq, Eq)]
622pub enum CatalogError {
623 SchemaNotEnabled,
625 ConstraintAlreadyExists,
627 LabelNotFound(String),
629 PropertyKeyNotFound(String),
631 EdgeTypeNotFound(String),
633 IndexNotFound(IndexId),
635}
636
637impl std::fmt::Display for CatalogError {
638 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
639 match self {
640 Self::SchemaNotEnabled => write!(f, "Schema constraints are not enabled"),
641 Self::ConstraintAlreadyExists => write!(f, "Constraint already exists"),
642 Self::LabelNotFound(name) => write!(f, "Label not found: {name}"),
643 Self::PropertyKeyNotFound(name) => write!(f, "Property key not found: {name}"),
644 Self::EdgeTypeNotFound(name) => write!(f, "Edge type not found: {name}"),
645 Self::IndexNotFound(id) => write!(f, "Index not found: {id}"),
646 }
647 }
648}
649
650impl std::error::Error for CatalogError {}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655 use std::thread;
656
657 #[test]
658 fn test_catalog_labels() {
659 let catalog = Catalog::new();
660
661 let person_id = catalog.get_or_create_label("Person");
663 let company_id = catalog.get_or_create_label("Company");
664
665 assert_ne!(person_id, company_id);
667
668 assert_eq!(catalog.get_or_create_label("Person"), person_id);
670
671 assert_eq!(catalog.get_label_id("Person"), Some(person_id));
673 assert_eq!(catalog.get_label_id("Company"), Some(company_id));
674 assert_eq!(catalog.get_label_id("Unknown"), None);
675
676 assert_eq!(catalog.get_label_name(person_id).as_deref(), Some("Person"));
678 assert_eq!(
679 catalog.get_label_name(company_id).as_deref(),
680 Some("Company")
681 );
682
683 assert_eq!(catalog.label_count(), 2);
685 }
686
687 #[test]
688 fn test_catalog_property_keys() {
689 let catalog = Catalog::new();
690
691 let name_id = catalog.get_or_create_property_key("name");
692 let age_id = catalog.get_or_create_property_key("age");
693
694 assert_ne!(name_id, age_id);
695 assert_eq!(catalog.get_or_create_property_key("name"), name_id);
696 assert_eq!(catalog.get_property_key_id("name"), Some(name_id));
697 assert_eq!(
698 catalog.get_property_key_name(name_id).as_deref(),
699 Some("name")
700 );
701 assert_eq!(catalog.property_key_count(), 2);
702 }
703
704 #[test]
705 fn test_catalog_edge_types() {
706 let catalog = Catalog::new();
707
708 let knows_id = catalog.get_or_create_edge_type("KNOWS");
709 let works_at_id = catalog.get_or_create_edge_type("WORKS_AT");
710
711 assert_ne!(knows_id, works_at_id);
712 assert_eq!(catalog.get_or_create_edge_type("KNOWS"), knows_id);
713 assert_eq!(catalog.get_edge_type_id("KNOWS"), Some(knows_id));
714 assert_eq!(
715 catalog.get_edge_type_name(knows_id).as_deref(),
716 Some("KNOWS")
717 );
718 assert_eq!(catalog.edge_type_count(), 2);
719 }
720
721 #[test]
722 fn test_catalog_indexes() {
723 let catalog = Catalog::new();
724
725 let person_id = catalog.get_or_create_label("Person");
726 let name_id = catalog.get_or_create_property_key("name");
727 let age_id = catalog.get_or_create_property_key("age");
728
729 let idx1 = catalog.create_index(person_id, name_id, IndexType::Hash);
731 let idx2 = catalog.create_index(person_id, age_id, IndexType::BTree);
732
733 assert_ne!(idx1, idx2);
734 assert_eq!(catalog.index_count(), 2);
735
736 let label_indexes = catalog.indexes_for_label(person_id);
738 assert_eq!(label_indexes.len(), 2);
739 assert!(label_indexes.contains(&idx1));
740 assert!(label_indexes.contains(&idx2));
741
742 let name_indexes = catalog.indexes_for_label_property(person_id, name_id);
744 assert_eq!(name_indexes.len(), 1);
745 assert_eq!(name_indexes[0], idx1);
746
747 let def = catalog.get_index(idx1).unwrap();
749 assert_eq!(def.label, person_id);
750 assert_eq!(def.property_key, name_id);
751 assert_eq!(def.index_type, IndexType::Hash);
752
753 assert!(catalog.drop_index(idx1));
755 assert_eq!(catalog.index_count(), 1);
756 assert!(catalog.get_index(idx1).is_none());
757 assert_eq!(catalog.indexes_for_label(person_id).len(), 1);
758 }
759
760 #[test]
761 fn test_catalog_schema_constraints() {
762 let catalog = Catalog::with_schema();
763
764 let person_id = catalog.get_or_create_label("Person");
765 let email_id = catalog.get_or_create_property_key("email");
766 let name_id = catalog.get_or_create_property_key("name");
767
768 assert!(catalog.add_unique_constraint(person_id, email_id).is_ok());
770 assert!(catalog.add_required_property(person_id, name_id).is_ok());
771
772 assert!(catalog.is_property_unique(person_id, email_id));
774 assert!(!catalog.is_property_unique(person_id, name_id));
775 assert!(catalog.is_property_required(person_id, name_id));
776 assert!(!catalog.is_property_required(person_id, email_id));
777
778 assert_eq!(
780 catalog.add_unique_constraint(person_id, email_id),
781 Err(CatalogError::ConstraintAlreadyExists)
782 );
783 }
784
785 #[test]
786 fn test_catalog_no_schema() {
787 let catalog = Catalog::new();
788
789 let person_id = catalog.get_or_create_label("Person");
790 let email_id = catalog.get_or_create_property_key("email");
791
792 assert_eq!(
794 catalog.add_unique_constraint(person_id, email_id),
795 Err(CatalogError::SchemaNotEnabled)
796 );
797 }
798
799 #[test]
802 fn test_catalog_default() {
803 let catalog = Catalog::default();
804 assert!(!catalog.has_schema());
805 assert_eq!(catalog.label_count(), 0);
806 assert_eq!(catalog.property_key_count(), 0);
807 assert_eq!(catalog.edge_type_count(), 0);
808 assert_eq!(catalog.index_count(), 0);
809 }
810
811 #[test]
812 fn test_catalog_all_labels() {
813 let catalog = Catalog::new();
814
815 catalog.get_or_create_label("Person");
816 catalog.get_or_create_label("Company");
817 catalog.get_or_create_label("Product");
818
819 let all = catalog.all_labels();
820 assert_eq!(all.len(), 3);
821 assert!(all.iter().any(|l| l.as_ref() == "Person"));
822 assert!(all.iter().any(|l| l.as_ref() == "Company"));
823 assert!(all.iter().any(|l| l.as_ref() == "Product"));
824 }
825
826 #[test]
827 fn test_catalog_all_property_keys() {
828 let catalog = Catalog::new();
829
830 catalog.get_or_create_property_key("name");
831 catalog.get_or_create_property_key("age");
832 catalog.get_or_create_property_key("email");
833
834 let all = catalog.all_property_keys();
835 assert_eq!(all.len(), 3);
836 assert!(all.iter().any(|k| k.as_ref() == "name"));
837 assert!(all.iter().any(|k| k.as_ref() == "age"));
838 assert!(all.iter().any(|k| k.as_ref() == "email"));
839 }
840
841 #[test]
842 fn test_catalog_all_edge_types() {
843 let catalog = Catalog::new();
844
845 catalog.get_or_create_edge_type("KNOWS");
846 catalog.get_or_create_edge_type("WORKS_AT");
847 catalog.get_or_create_edge_type("LIVES_IN");
848
849 let all = catalog.all_edge_types();
850 assert_eq!(all.len(), 3);
851 assert!(all.iter().any(|t| t.as_ref() == "KNOWS"));
852 assert!(all.iter().any(|t| t.as_ref() == "WORKS_AT"));
853 assert!(all.iter().any(|t| t.as_ref() == "LIVES_IN"));
854 }
855
856 #[test]
857 fn test_catalog_invalid_id_lookup() {
858 let catalog = Catalog::new();
859
860 let _ = catalog.get_or_create_label("Person");
862
863 let invalid_label = LabelId::new(999);
865 let invalid_property = PropertyKeyId::new(999);
866 let invalid_edge_type = EdgeTypeId::new(999);
867 let invalid_index = IndexId::new(999);
868
869 assert!(catalog.get_label_name(invalid_label).is_none());
870 assert!(catalog.get_property_key_name(invalid_property).is_none());
871 assert!(catalog.get_edge_type_name(invalid_edge_type).is_none());
872 assert!(catalog.get_index(invalid_index).is_none());
873 }
874
875 #[test]
876 fn test_catalog_drop_nonexistent_index() {
877 let catalog = Catalog::new();
878 let invalid_index = IndexId::new(999);
879 assert!(!catalog.drop_index(invalid_index));
880 }
881
882 #[test]
883 fn test_catalog_indexes_for_nonexistent_label() {
884 let catalog = Catalog::new();
885 let invalid_label = LabelId::new(999);
886 let invalid_property = PropertyKeyId::new(999);
887
888 assert!(catalog.indexes_for_label(invalid_label).is_empty());
889 assert!(
890 catalog
891 .indexes_for_label_property(invalid_label, invalid_property)
892 .is_empty()
893 );
894 }
895
896 #[test]
897 fn test_catalog_multiple_indexes_same_property() {
898 let catalog = Catalog::new();
899
900 let person_id = catalog.get_or_create_label("Person");
901 let name_id = catalog.get_or_create_property_key("name");
902
903 let hash_idx = catalog.create_index(person_id, name_id, IndexType::Hash);
905 let btree_idx = catalog.create_index(person_id, name_id, IndexType::BTree);
906 let fulltext_idx = catalog.create_index(person_id, name_id, IndexType::FullText);
907
908 assert_eq!(catalog.index_count(), 3);
909
910 let indexes = catalog.indexes_for_label_property(person_id, name_id);
911 assert_eq!(indexes.len(), 3);
912 assert!(indexes.contains(&hash_idx));
913 assert!(indexes.contains(&btree_idx));
914 assert!(indexes.contains(&fulltext_idx));
915
916 assert_eq!(
918 catalog.get_index(hash_idx).unwrap().index_type,
919 IndexType::Hash
920 );
921 assert_eq!(
922 catalog.get_index(btree_idx).unwrap().index_type,
923 IndexType::BTree
924 );
925 assert_eq!(
926 catalog.get_index(fulltext_idx).unwrap().index_type,
927 IndexType::FullText
928 );
929 }
930
931 #[test]
932 fn test_catalog_schema_required_property_duplicate() {
933 let catalog = Catalog::with_schema();
934
935 let person_id = catalog.get_or_create_label("Person");
936 let name_id = catalog.get_or_create_property_key("name");
937
938 assert!(catalog.add_required_property(person_id, name_id).is_ok());
940
941 assert_eq!(
943 catalog.add_required_property(person_id, name_id),
944 Err(CatalogError::ConstraintAlreadyExists)
945 );
946 }
947
948 #[test]
949 fn test_catalog_schema_check_without_constraints() {
950 let catalog = Catalog::new();
951
952 let person_id = catalog.get_or_create_label("Person");
953 let name_id = catalog.get_or_create_property_key("name");
954
955 assert!(!catalog.is_property_unique(person_id, name_id));
957 assert!(!catalog.is_property_required(person_id, name_id));
958 }
959
960 #[test]
961 fn test_catalog_has_schema() {
962 let without_schema = Catalog::new();
963 assert!(!without_schema.has_schema());
964
965 let with_schema = Catalog::with_schema();
966 assert!(with_schema.has_schema());
967 }
968
969 #[test]
970 fn test_catalog_error_display() {
971 assert_eq!(
972 CatalogError::SchemaNotEnabled.to_string(),
973 "Schema constraints are not enabled"
974 );
975 assert_eq!(
976 CatalogError::ConstraintAlreadyExists.to_string(),
977 "Constraint already exists"
978 );
979 assert_eq!(
980 CatalogError::LabelNotFound("Person".to_string()).to_string(),
981 "Label not found: Person"
982 );
983 assert_eq!(
984 CatalogError::PropertyKeyNotFound("name".to_string()).to_string(),
985 "Property key not found: name"
986 );
987 assert_eq!(
988 CatalogError::EdgeTypeNotFound("KNOWS".to_string()).to_string(),
989 "Edge type not found: KNOWS"
990 );
991 let idx = IndexId::new(42);
992 assert!(CatalogError::IndexNotFound(idx).to_string().contains("42"));
993 }
994
995 #[test]
996 fn test_catalog_concurrent_label_creation() {
997 use std::sync::Arc;
998
999 let catalog = Arc::new(Catalog::new());
1000 let mut handles = vec![];
1001
1002 for i in 0..10 {
1004 let catalog = Arc::clone(&catalog);
1005 handles.push(thread::spawn(move || {
1006 let label_name = format!("Label{}", i % 3); catalog.get_or_create_label(&label_name)
1008 }));
1009 }
1010
1011 let mut ids: Vec<LabelId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
1012 ids.sort_by_key(|id| id.as_u32());
1013 ids.dedup();
1014
1015 assert_eq!(ids.len(), 3);
1017 assert_eq!(catalog.label_count(), 3);
1018 }
1019
1020 #[test]
1021 fn test_catalog_concurrent_property_key_creation() {
1022 use std::sync::Arc;
1023
1024 let catalog = Arc::new(Catalog::new());
1025 let mut handles = vec![];
1026
1027 for i in 0..10 {
1028 let catalog = Arc::clone(&catalog);
1029 handles.push(thread::spawn(move || {
1030 let key_name = format!("key{}", i % 4);
1031 catalog.get_or_create_property_key(&key_name)
1032 }));
1033 }
1034
1035 let mut ids: Vec<PropertyKeyId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
1036 ids.sort_by_key(|id| id.as_u32());
1037 ids.dedup();
1038
1039 assert_eq!(ids.len(), 4);
1040 assert_eq!(catalog.property_key_count(), 4);
1041 }
1042
1043 #[test]
1044 fn test_catalog_concurrent_index_operations() {
1045 use std::sync::Arc;
1046
1047 let catalog = Arc::new(Catalog::new());
1048 let label = catalog.get_or_create_label("Node");
1049
1050 let mut handles = vec![];
1051
1052 for i in 0..5 {
1054 let catalog = Arc::clone(&catalog);
1055 handles.push(thread::spawn(move || {
1056 let prop = PropertyKeyId::new(i);
1057 catalog.create_index(label, prop, IndexType::Hash)
1058 }));
1059 }
1060
1061 let ids: Vec<IndexId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
1062 assert_eq!(ids.len(), 5);
1063 assert_eq!(catalog.index_count(), 5);
1064 }
1065
1066 #[test]
1067 fn test_catalog_special_characters_in_names() {
1068 let catalog = Catalog::new();
1069
1070 let label1 = catalog.get_or_create_label("Label With Spaces");
1072 let label2 = catalog.get_or_create_label("Label-With-Dashes");
1073 let label3 = catalog.get_or_create_label("Label_With_Underscores");
1074 let label4 = catalog.get_or_create_label("LabelWithUnicode\u{00E9}");
1075
1076 assert_ne!(label1, label2);
1077 assert_ne!(label2, label3);
1078 assert_ne!(label3, label4);
1079
1080 assert_eq!(
1081 catalog.get_label_name(label1).as_deref(),
1082 Some("Label With Spaces")
1083 );
1084 assert_eq!(
1085 catalog.get_label_name(label4).as_deref(),
1086 Some("LabelWithUnicode\u{00E9}")
1087 );
1088 }
1089
1090 #[test]
1091 fn test_catalog_empty_names() {
1092 let catalog = Catalog::new();
1093
1094 let empty_label = catalog.get_or_create_label("");
1096 let empty_prop = catalog.get_or_create_property_key("");
1097 let empty_edge = catalog.get_or_create_edge_type("");
1098
1099 assert_eq!(catalog.get_label_name(empty_label).as_deref(), Some(""));
1100 assert_eq!(
1101 catalog.get_property_key_name(empty_prop).as_deref(),
1102 Some("")
1103 );
1104 assert_eq!(catalog.get_edge_type_name(empty_edge).as_deref(), Some(""));
1105
1106 assert_eq!(catalog.get_or_create_label(""), empty_label);
1108 }
1109
1110 #[test]
1111 fn test_catalog_large_number_of_entries() {
1112 let catalog = Catalog::new();
1113
1114 for i in 0..1000 {
1116 catalog.get_or_create_label(&format!("Label{}", i));
1117 }
1118
1119 assert_eq!(catalog.label_count(), 1000);
1120
1121 let all = catalog.all_labels();
1123 assert_eq!(all.len(), 1000);
1124
1125 let id = catalog.get_label_id("Label500").unwrap();
1127 assert_eq!(catalog.get_label_name(id).as_deref(), Some("Label500"));
1128 }
1129
1130 #[test]
1131 fn test_index_definition_debug() {
1132 let def = IndexDefinition {
1133 id: IndexId::new(1),
1134 label: LabelId::new(2),
1135 property_key: PropertyKeyId::new(3),
1136 index_type: IndexType::Hash,
1137 };
1138
1139 let debug_str = format!("{:?}", def);
1141 assert!(debug_str.contains("IndexDefinition"));
1142 assert!(debug_str.contains("Hash"));
1143 }
1144
1145 #[test]
1146 fn test_index_type_equality() {
1147 assert_eq!(IndexType::Hash, IndexType::Hash);
1148 assert_ne!(IndexType::Hash, IndexType::BTree);
1149 assert_ne!(IndexType::BTree, IndexType::FullText);
1150
1151 let t = IndexType::Hash;
1153 let t2 = t;
1154 assert_eq!(t, t2);
1155 }
1156
1157 #[test]
1158 fn test_catalog_error_equality() {
1159 assert_eq!(
1160 CatalogError::SchemaNotEnabled,
1161 CatalogError::SchemaNotEnabled
1162 );
1163 assert_eq!(
1164 CatalogError::ConstraintAlreadyExists,
1165 CatalogError::ConstraintAlreadyExists
1166 );
1167 assert_eq!(
1168 CatalogError::LabelNotFound("X".to_string()),
1169 CatalogError::LabelNotFound("X".to_string())
1170 );
1171 assert_ne!(
1172 CatalogError::LabelNotFound("X".to_string()),
1173 CatalogError::LabelNotFound("Y".to_string())
1174 );
1175 }
1176}