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