1use std::collections::{HashMap, HashSet};
15use std::sync::Arc;
16use std::sync::atomic::{AtomicU32, Ordering};
17
18use parking_lot::RwLock;
19
20use grafeo_common::types::{EdgeTypeId, IndexId, LabelId, PropertyKeyId, Value};
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: Some(SchemaCatalog::new()),
49 }
50 }
51
52 #[must_use]
56 pub fn with_schema() -> Self {
57 Self::new()
58 }
59
60 pub fn get_or_create_label(&self, name: &str) -> LabelId {
64 self.labels.get_or_create(name)
65 }
66
67 #[must_use]
69 pub fn get_label_id(&self, name: &str) -> Option<LabelId> {
70 self.labels.get_id(name)
71 }
72
73 #[must_use]
75 pub fn get_label_name(&self, id: LabelId) -> Option<Arc<str>> {
76 self.labels.get_name(id)
77 }
78
79 #[must_use]
81 pub fn label_count(&self) -> usize {
82 self.labels.count()
83 }
84
85 #[must_use]
87 pub fn all_labels(&self) -> Vec<Arc<str>> {
88 self.labels.all_names()
89 }
90
91 pub fn get_or_create_property_key(&self, name: &str) -> PropertyKeyId {
95 self.property_keys.get_or_create(name)
96 }
97
98 #[must_use]
100 pub fn get_property_key_id(&self, name: &str) -> Option<PropertyKeyId> {
101 self.property_keys.get_id(name)
102 }
103
104 #[must_use]
106 pub fn get_property_key_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
107 self.property_keys.get_name(id)
108 }
109
110 #[must_use]
112 pub fn property_key_count(&self) -> usize {
113 self.property_keys.count()
114 }
115
116 #[must_use]
118 pub fn all_property_keys(&self) -> Vec<Arc<str>> {
119 self.property_keys.all_names()
120 }
121
122 pub fn get_or_create_edge_type(&self, name: &str) -> EdgeTypeId {
126 self.edge_types.get_or_create(name)
127 }
128
129 #[must_use]
131 pub fn get_edge_type_id(&self, name: &str) -> Option<EdgeTypeId> {
132 self.edge_types.get_id(name)
133 }
134
135 #[must_use]
137 pub fn get_edge_type_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
138 self.edge_types.get_name(id)
139 }
140
141 #[must_use]
143 pub fn edge_type_count(&self) -> usize {
144 self.edge_types.count()
145 }
146
147 #[must_use]
149 pub fn all_edge_types(&self) -> Vec<Arc<str>> {
150 self.edge_types.all_names()
151 }
152
153 pub fn create_index(
157 &self,
158 label: LabelId,
159 property_key: PropertyKeyId,
160 index_type: IndexType,
161 ) -> IndexId {
162 self.indexes.create(label, property_key, index_type)
163 }
164
165 pub fn drop_index(&self, id: IndexId) -> bool {
167 self.indexes.drop(id)
168 }
169
170 #[must_use]
172 pub fn get_index(&self, id: IndexId) -> Option<IndexDefinition> {
173 self.indexes.get(id)
174 }
175
176 #[must_use]
178 pub fn indexes_for_label(&self, label: LabelId) -> Vec<IndexId> {
179 self.indexes.for_label(label)
180 }
181
182 #[must_use]
184 pub fn indexes_for_label_property(
185 &self,
186 label: LabelId,
187 property_key: PropertyKeyId,
188 ) -> Vec<IndexId> {
189 self.indexes.for_label_property(label, property_key)
190 }
191
192 #[must_use]
194 pub fn all_indexes(&self) -> Vec<IndexDefinition> {
195 self.indexes.all()
196 }
197
198 #[must_use]
200 pub fn index_count(&self) -> usize {
201 self.indexes.count()
202 }
203
204 #[must_use]
208 pub fn has_schema(&self) -> bool {
209 self.schema.is_some()
210 }
211
212 pub fn add_unique_constraint(
216 &self,
217 label: LabelId,
218 property_key: PropertyKeyId,
219 ) -> Result<(), CatalogError> {
220 match &self.schema {
221 Some(schema) => schema.add_unique_constraint(label, property_key),
222 None => Err(CatalogError::SchemaNotEnabled),
223 }
224 }
225
226 pub fn add_required_property(
230 &self,
231 label: LabelId,
232 property_key: PropertyKeyId,
233 ) -> Result<(), CatalogError> {
234 match &self.schema {
235 Some(schema) => schema.add_required_property(label, property_key),
236 None => Err(CatalogError::SchemaNotEnabled),
237 }
238 }
239
240 #[must_use]
242 pub fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
243 self.schema
244 .as_ref()
245 .is_some_and(|s| s.is_property_required(label, property_key))
246 }
247
248 #[must_use]
250 pub fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
251 self.schema
252 .as_ref()
253 .is_some_and(|s| s.is_property_unique(label, property_key))
254 }
255
256 #[must_use]
260 pub fn schema(&self) -> Option<&SchemaCatalog> {
261 self.schema.as_ref()
262 }
263
264 pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
266 match &self.schema {
267 Some(schema) => schema.register_node_type(def),
268 None => Err(CatalogError::SchemaNotEnabled),
269 }
270 }
271
272 pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
274 if let Some(schema) = &self.schema {
275 schema.register_or_replace_node_type(def);
276 }
277 }
278
279 pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
281 match &self.schema {
282 Some(schema) => schema.drop_node_type(name),
283 None => Err(CatalogError::SchemaNotEnabled),
284 }
285 }
286
287 #[must_use]
289 pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
290 self.schema.as_ref().and_then(|s| s.get_node_type(name))
291 }
292
293 #[must_use]
295 pub fn all_node_type_names(&self) -> Vec<String> {
296 self.schema
297 .as_ref()
298 .map(SchemaCatalog::all_node_types)
299 .unwrap_or_default()
300 }
301
302 #[must_use]
304 pub fn all_edge_type_names(&self) -> Vec<String> {
305 self.schema
306 .as_ref()
307 .map(SchemaCatalog::all_edge_types)
308 .unwrap_or_default()
309 }
310
311 pub fn register_edge_type_def(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
313 match &self.schema {
314 Some(schema) => schema.register_edge_type(def),
315 None => Err(CatalogError::SchemaNotEnabled),
316 }
317 }
318
319 pub fn register_or_replace_edge_type_def(&self, def: EdgeTypeDefinition) {
321 if let Some(schema) = &self.schema {
322 schema.register_or_replace_edge_type(def);
323 }
324 }
325
326 pub fn drop_edge_type_def(&self, name: &str) -> Result<(), CatalogError> {
328 match &self.schema {
329 Some(schema) => schema.drop_edge_type(name),
330 None => Err(CatalogError::SchemaNotEnabled),
331 }
332 }
333
334 #[must_use]
336 pub fn get_edge_type_def(&self, name: &str) -> Option<EdgeTypeDefinition> {
337 self.schema.as_ref().and_then(|s| s.get_edge_type(name))
338 }
339
340 pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
342 match &self.schema {
343 Some(schema) => schema.register_graph_type(def),
344 None => Err(CatalogError::SchemaNotEnabled),
345 }
346 }
347
348 pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
350 match &self.schema {
351 Some(schema) => schema.drop_graph_type(name),
352 None => Err(CatalogError::SchemaNotEnabled),
353 }
354 }
355
356 pub fn register_schema_namespace(&self, name: String) -> Result<(), CatalogError> {
358 match &self.schema {
359 Some(schema) => schema.register_schema(name),
360 None => Err(CatalogError::SchemaNotEnabled),
361 }
362 }
363
364 pub fn drop_schema_namespace(&self, name: &str) -> Result<(), CatalogError> {
366 match &self.schema {
367 Some(schema) => schema.drop_schema(name),
368 None => Err(CatalogError::SchemaNotEnabled),
369 }
370 }
371
372 pub fn alter_node_type_add_property(
374 &self,
375 type_name: &str,
376 property: TypedProperty,
377 ) -> Result<(), CatalogError> {
378 match &self.schema {
379 Some(schema) => schema.alter_node_type_add_property(type_name, property),
380 None => Err(CatalogError::SchemaNotEnabled),
381 }
382 }
383
384 pub fn alter_node_type_drop_property(
386 &self,
387 type_name: &str,
388 property_name: &str,
389 ) -> Result<(), CatalogError> {
390 match &self.schema {
391 Some(schema) => schema.alter_node_type_drop_property(type_name, property_name),
392 None => Err(CatalogError::SchemaNotEnabled),
393 }
394 }
395
396 pub fn alter_edge_type_add_property(
398 &self,
399 type_name: &str,
400 property: TypedProperty,
401 ) -> Result<(), CatalogError> {
402 match &self.schema {
403 Some(schema) => schema.alter_edge_type_add_property(type_name, property),
404 None => Err(CatalogError::SchemaNotEnabled),
405 }
406 }
407
408 pub fn alter_edge_type_drop_property(
410 &self,
411 type_name: &str,
412 property_name: &str,
413 ) -> Result<(), CatalogError> {
414 match &self.schema {
415 Some(schema) => schema.alter_edge_type_drop_property(type_name, property_name),
416 None => Err(CatalogError::SchemaNotEnabled),
417 }
418 }
419
420 pub fn alter_graph_type_add_node_type(
422 &self,
423 graph_type_name: &str,
424 node_type: String,
425 ) -> Result<(), CatalogError> {
426 match &self.schema {
427 Some(schema) => schema.alter_graph_type_add_node_type(graph_type_name, node_type),
428 None => Err(CatalogError::SchemaNotEnabled),
429 }
430 }
431
432 pub fn alter_graph_type_drop_node_type(
434 &self,
435 graph_type_name: &str,
436 node_type: &str,
437 ) -> Result<(), CatalogError> {
438 match &self.schema {
439 Some(schema) => schema.alter_graph_type_drop_node_type(graph_type_name, node_type),
440 None => Err(CatalogError::SchemaNotEnabled),
441 }
442 }
443
444 pub fn alter_graph_type_add_edge_type(
446 &self,
447 graph_type_name: &str,
448 edge_type: String,
449 ) -> Result<(), CatalogError> {
450 match &self.schema {
451 Some(schema) => schema.alter_graph_type_add_edge_type(graph_type_name, edge_type),
452 None => Err(CatalogError::SchemaNotEnabled),
453 }
454 }
455
456 pub fn alter_graph_type_drop_edge_type(
458 &self,
459 graph_type_name: &str,
460 edge_type: &str,
461 ) -> Result<(), CatalogError> {
462 match &self.schema {
463 Some(schema) => schema.alter_graph_type_drop_edge_type(graph_type_name, edge_type),
464 None => Err(CatalogError::SchemaNotEnabled),
465 }
466 }
467
468 pub fn bind_graph_type(
470 &self,
471 graph_name: &str,
472 graph_type: String,
473 ) -> Result<(), CatalogError> {
474 match &self.schema {
475 Some(schema) => {
476 if schema.get_graph_type(&graph_type).is_none() {
478 return Err(CatalogError::TypeNotFound(graph_type));
479 }
480 schema
481 .graph_type_bindings
482 .write()
483 .insert(graph_name.to_string(), graph_type);
484 Ok(())
485 }
486 None => Err(CatalogError::SchemaNotEnabled),
487 }
488 }
489
490 pub fn get_graph_type_binding(&self, graph_name: &str) -> Option<String> {
492 self.schema
493 .as_ref()?
494 .graph_type_bindings
495 .read()
496 .get(graph_name)
497 .cloned()
498 }
499
500 pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
502 match &self.schema {
503 Some(schema) => schema.register_procedure(def),
504 None => Err(CatalogError::SchemaNotEnabled),
505 }
506 }
507
508 pub fn replace_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
510 match &self.schema {
511 Some(schema) => {
512 schema.replace_procedure(def);
513 Ok(())
514 }
515 None => Err(CatalogError::SchemaNotEnabled),
516 }
517 }
518
519 pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
521 match &self.schema {
522 Some(schema) => schema.drop_procedure(name),
523 None => Err(CatalogError::SchemaNotEnabled),
524 }
525 }
526
527 pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
529 self.schema.as_ref()?.get_procedure(name)
530 }
531}
532
533impl Default for Catalog {
534 fn default() -> Self {
535 Self::new()
536 }
537}
538
539struct LabelCatalog {
543 name_to_id: RwLock<HashMap<Arc<str>, LabelId>>,
544 id_to_name: RwLock<Vec<Arc<str>>>,
545 next_id: AtomicU32,
546}
547
548impl LabelCatalog {
549 fn new() -> Self {
550 Self {
551 name_to_id: RwLock::new(HashMap::new()),
552 id_to_name: RwLock::new(Vec::new()),
553 next_id: AtomicU32::new(0),
554 }
555 }
556
557 fn get_or_create(&self, name: &str) -> LabelId {
558 {
560 let name_to_id = self.name_to_id.read();
561 if let Some(&id) = name_to_id.get(name) {
562 return id;
563 }
564 }
565
566 let mut name_to_id = self.name_to_id.write();
568 let mut id_to_name = self.id_to_name.write();
569
570 if let Some(&id) = name_to_id.get(name) {
572 return id;
573 }
574
575 let id = LabelId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
576 let name: Arc<str> = name.into();
577 name_to_id.insert(Arc::clone(&name), id);
578 id_to_name.push(name);
579 id
580 }
581
582 fn get_id(&self, name: &str) -> Option<LabelId> {
583 self.name_to_id.read().get(name).copied()
584 }
585
586 fn get_name(&self, id: LabelId) -> Option<Arc<str>> {
587 self.id_to_name.read().get(id.as_u32() as usize).cloned()
588 }
589
590 fn count(&self) -> usize {
591 self.id_to_name.read().len()
592 }
593
594 fn all_names(&self) -> Vec<Arc<str>> {
595 self.id_to_name.read().clone()
596 }
597}
598
599struct PropertyCatalog {
603 name_to_id: RwLock<HashMap<Arc<str>, PropertyKeyId>>,
604 id_to_name: RwLock<Vec<Arc<str>>>,
605 next_id: AtomicU32,
606}
607
608impl PropertyCatalog {
609 fn new() -> Self {
610 Self {
611 name_to_id: RwLock::new(HashMap::new()),
612 id_to_name: RwLock::new(Vec::new()),
613 next_id: AtomicU32::new(0),
614 }
615 }
616
617 fn get_or_create(&self, name: &str) -> PropertyKeyId {
618 {
620 let name_to_id = self.name_to_id.read();
621 if let Some(&id) = name_to_id.get(name) {
622 return id;
623 }
624 }
625
626 let mut name_to_id = self.name_to_id.write();
628 let mut id_to_name = self.id_to_name.write();
629
630 if let Some(&id) = name_to_id.get(name) {
632 return id;
633 }
634
635 let id = PropertyKeyId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
636 let name: Arc<str> = name.into();
637 name_to_id.insert(Arc::clone(&name), id);
638 id_to_name.push(name);
639 id
640 }
641
642 fn get_id(&self, name: &str) -> Option<PropertyKeyId> {
643 self.name_to_id.read().get(name).copied()
644 }
645
646 fn get_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
647 self.id_to_name.read().get(id.as_u32() as usize).cloned()
648 }
649
650 fn count(&self) -> usize {
651 self.id_to_name.read().len()
652 }
653
654 fn all_names(&self) -> Vec<Arc<str>> {
655 self.id_to_name.read().clone()
656 }
657}
658
659struct EdgeTypeCatalog {
663 name_to_id: RwLock<HashMap<Arc<str>, EdgeTypeId>>,
664 id_to_name: RwLock<Vec<Arc<str>>>,
665 next_id: AtomicU32,
666}
667
668impl EdgeTypeCatalog {
669 fn new() -> Self {
670 Self {
671 name_to_id: RwLock::new(HashMap::new()),
672 id_to_name: RwLock::new(Vec::new()),
673 next_id: AtomicU32::new(0),
674 }
675 }
676
677 fn get_or_create(&self, name: &str) -> EdgeTypeId {
678 {
680 let name_to_id = self.name_to_id.read();
681 if let Some(&id) = name_to_id.get(name) {
682 return id;
683 }
684 }
685
686 let mut name_to_id = self.name_to_id.write();
688 let mut id_to_name = self.id_to_name.write();
689
690 if let Some(&id) = name_to_id.get(name) {
692 return id;
693 }
694
695 let id = EdgeTypeId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
696 let name: Arc<str> = name.into();
697 name_to_id.insert(Arc::clone(&name), id);
698 id_to_name.push(name);
699 id
700 }
701
702 fn get_id(&self, name: &str) -> Option<EdgeTypeId> {
703 self.name_to_id.read().get(name).copied()
704 }
705
706 fn get_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
707 self.id_to_name.read().get(id.as_u32() as usize).cloned()
708 }
709
710 fn count(&self) -> usize {
711 self.id_to_name.read().len()
712 }
713
714 fn all_names(&self) -> Vec<Arc<str>> {
715 self.id_to_name.read().clone()
716 }
717}
718
719#[derive(Debug, Clone, Copy, PartialEq, Eq)]
723pub enum IndexType {
724 Hash,
726 BTree,
728 FullText,
730}
731
732#[derive(Debug, Clone)]
734pub struct IndexDefinition {
735 pub id: IndexId,
737 pub label: LabelId,
739 pub property_key: PropertyKeyId,
741 pub index_type: IndexType,
743}
744
745struct IndexCatalog {
747 indexes: RwLock<HashMap<IndexId, IndexDefinition>>,
748 label_indexes: RwLock<HashMap<LabelId, Vec<IndexId>>>,
749 label_property_indexes: RwLock<HashMap<(LabelId, PropertyKeyId), Vec<IndexId>>>,
750 next_id: AtomicU32,
751}
752
753impl IndexCatalog {
754 fn new() -> Self {
755 Self {
756 indexes: RwLock::new(HashMap::new()),
757 label_indexes: RwLock::new(HashMap::new()),
758 label_property_indexes: RwLock::new(HashMap::new()),
759 next_id: AtomicU32::new(0),
760 }
761 }
762
763 fn create(
764 &self,
765 label: LabelId,
766 property_key: PropertyKeyId,
767 index_type: IndexType,
768 ) -> IndexId {
769 let id = IndexId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
770 let definition = IndexDefinition {
771 id,
772 label,
773 property_key,
774 index_type,
775 };
776
777 let mut indexes = self.indexes.write();
778 let mut label_indexes = self.label_indexes.write();
779 let mut label_property_indexes = self.label_property_indexes.write();
780
781 indexes.insert(id, definition);
782 label_indexes.entry(label).or_default().push(id);
783 label_property_indexes
784 .entry((label, property_key))
785 .or_default()
786 .push(id);
787
788 id
789 }
790
791 fn drop(&self, id: IndexId) -> bool {
792 let mut indexes = self.indexes.write();
793 let mut label_indexes = self.label_indexes.write();
794 let mut label_property_indexes = self.label_property_indexes.write();
795
796 if let Some(definition) = indexes.remove(&id) {
797 if let Some(ids) = label_indexes.get_mut(&definition.label) {
799 ids.retain(|&i| i != id);
800 }
801 if let Some(ids) =
803 label_property_indexes.get_mut(&(definition.label, definition.property_key))
804 {
805 ids.retain(|&i| i != id);
806 }
807 true
808 } else {
809 false
810 }
811 }
812
813 fn get(&self, id: IndexId) -> Option<IndexDefinition> {
814 self.indexes.read().get(&id).cloned()
815 }
816
817 fn for_label(&self, label: LabelId) -> Vec<IndexId> {
818 self.label_indexes
819 .read()
820 .get(&label)
821 .cloned()
822 .unwrap_or_default()
823 }
824
825 fn for_label_property(&self, label: LabelId, property_key: PropertyKeyId) -> Vec<IndexId> {
826 self.label_property_indexes
827 .read()
828 .get(&(label, property_key))
829 .cloned()
830 .unwrap_or_default()
831 }
832
833 fn count(&self) -> usize {
834 self.indexes.read().len()
835 }
836
837 fn all(&self) -> Vec<IndexDefinition> {
838 self.indexes.read().values().cloned().collect()
839 }
840}
841
842#[derive(Debug, Clone, PartialEq, Eq)]
846pub enum PropertyDataType {
847 String,
849 Int64,
851 Float64,
853 Bool,
855 Date,
857 Time,
859 Timestamp,
861 Duration,
863 List,
865 ListTyped(Box<PropertyDataType>),
867 Map,
869 Bytes,
871 Node,
873 Edge,
875 Any,
877}
878
879impl PropertyDataType {
880 #[must_use]
882 pub fn from_type_name(name: &str) -> Self {
883 let upper = name.to_uppercase();
884 if let Some(inner) = upper
886 .strip_prefix("LIST<")
887 .and_then(|s| s.strip_suffix('>'))
888 {
889 return Self::ListTyped(Box::new(Self::from_type_name(inner)));
890 }
891 match upper.as_str() {
892 "STRING" | "VARCHAR" | "TEXT" => Self::String,
893 "INT" | "INT64" | "INTEGER" | "BIGINT" => Self::Int64,
894 "FLOAT" | "FLOAT64" | "DOUBLE" | "REAL" => Self::Float64,
895 "BOOL" | "BOOLEAN" => Self::Bool,
896 "DATE" => Self::Date,
897 "TIME" => Self::Time,
898 "TIMESTAMP" | "DATETIME" => Self::Timestamp,
899 "DURATION" | "INTERVAL" => Self::Duration,
900 "LIST" | "ARRAY" => Self::List,
901 "MAP" | "RECORD" => Self::Map,
902 "BYTES" | "BINARY" | "BLOB" => Self::Bytes,
903 "NODE" => Self::Node,
904 "EDGE" | "RELATIONSHIP" => Self::Edge,
905 _ => Self::Any,
906 }
907 }
908
909 #[must_use]
911 pub fn matches(&self, value: &Value) -> bool {
912 match (self, value) {
913 (Self::Any, _) | (_, Value::Null) => true,
914 (Self::String, Value::String(_)) => true,
915 (Self::Int64, Value::Int64(_)) => true,
916 (Self::Float64, Value::Float64(_)) => true,
917 (Self::Bool, Value::Bool(_)) => true,
918 (Self::Date, Value::Date(_)) => true,
919 (Self::Time, Value::Time(_)) => true,
920 (Self::Timestamp, Value::Timestamp(_)) => true,
921 (Self::Duration, Value::Duration(_)) => true,
922 (Self::List, Value::List(_)) => true,
923 (Self::ListTyped(elem_type), Value::List(items)) => {
924 items.iter().all(|item| elem_type.matches(item))
925 }
926 (Self::Bytes, Value::Bytes(_)) => true,
927 (Self::Node | Self::Edge, Value::Map(_)) => true,
930 _ => false,
931 }
932 }
933}
934
935impl std::fmt::Display for PropertyDataType {
936 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
937 match self {
938 Self::String => write!(f, "STRING"),
939 Self::Int64 => write!(f, "INT64"),
940 Self::Float64 => write!(f, "FLOAT64"),
941 Self::Bool => write!(f, "BOOLEAN"),
942 Self::Date => write!(f, "DATE"),
943 Self::Time => write!(f, "TIME"),
944 Self::Timestamp => write!(f, "TIMESTAMP"),
945 Self::Duration => write!(f, "DURATION"),
946 Self::List => write!(f, "LIST"),
947 Self::ListTyped(elem) => write!(f, "LIST<{elem}>"),
948 Self::Map => write!(f, "MAP"),
949 Self::Bytes => write!(f, "BYTES"),
950 Self::Node => write!(f, "NODE"),
951 Self::Edge => write!(f, "EDGE"),
952 Self::Any => write!(f, "ANY"),
953 }
954 }
955}
956
957#[derive(Debug, Clone)]
959pub struct TypedProperty {
960 pub name: String,
962 pub data_type: PropertyDataType,
964 pub nullable: bool,
966 pub default_value: Option<Value>,
968}
969
970#[derive(Debug, Clone)]
972pub enum TypeConstraint {
973 PrimaryKey(Vec<String>),
975 Unique(Vec<String>),
977 NotNull(String),
979 Check {
981 name: Option<String>,
983 expression: String,
985 },
986}
987
988#[derive(Debug, Clone)]
990pub struct NodeTypeDefinition {
991 pub name: String,
993 pub properties: Vec<TypedProperty>,
995 pub constraints: Vec<TypeConstraint>,
997}
998
999#[derive(Debug, Clone)]
1001pub struct EdgeTypeDefinition {
1002 pub name: String,
1004 pub properties: Vec<TypedProperty>,
1006 pub constraints: Vec<TypeConstraint>,
1008}
1009
1010#[derive(Debug, Clone)]
1012pub struct GraphTypeDefinition {
1013 pub name: String,
1015 pub allowed_node_types: Vec<String>,
1017 pub allowed_edge_types: Vec<String>,
1019 pub open: bool,
1021}
1022
1023#[derive(Debug, Clone)]
1025pub struct ProcedureDefinition {
1026 pub name: String,
1028 pub params: Vec<(String, String)>,
1030 pub returns: Vec<(String, String)>,
1032 pub body: String,
1034}
1035
1036pub struct SchemaCatalog {
1040 unique_constraints: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1042 required_properties: RwLock<HashSet<(LabelId, PropertyKeyId)>>,
1044 node_types: RwLock<HashMap<String, NodeTypeDefinition>>,
1046 edge_types: RwLock<HashMap<String, EdgeTypeDefinition>>,
1048 graph_types: RwLock<HashMap<String, GraphTypeDefinition>>,
1050 schemas: RwLock<Vec<String>>,
1052 graph_type_bindings: RwLock<HashMap<String, String>>,
1054 procedures: RwLock<HashMap<String, ProcedureDefinition>>,
1056}
1057
1058impl SchemaCatalog {
1059 fn new() -> Self {
1060 Self {
1061 unique_constraints: RwLock::new(HashSet::new()),
1062 required_properties: RwLock::new(HashSet::new()),
1063 node_types: RwLock::new(HashMap::new()),
1064 edge_types: RwLock::new(HashMap::new()),
1065 graph_types: RwLock::new(HashMap::new()),
1066 schemas: RwLock::new(Vec::new()),
1067 graph_type_bindings: RwLock::new(HashMap::new()),
1068 procedures: RwLock::new(HashMap::new()),
1069 }
1070 }
1071
1072 pub fn register_node_type(&self, def: NodeTypeDefinition) -> Result<(), CatalogError> {
1076 let mut types = self.node_types.write();
1077 if types.contains_key(&def.name) {
1078 return Err(CatalogError::TypeAlreadyExists(def.name));
1079 }
1080 types.insert(def.name.clone(), def);
1081 Ok(())
1082 }
1083
1084 pub fn register_or_replace_node_type(&self, def: NodeTypeDefinition) {
1086 self.node_types.write().insert(def.name.clone(), def);
1087 }
1088
1089 pub fn drop_node_type(&self, name: &str) -> Result<(), CatalogError> {
1091 let mut types = self.node_types.write();
1092 if types.remove(name).is_none() {
1093 return Err(CatalogError::TypeNotFound(name.to_string()));
1094 }
1095 Ok(())
1096 }
1097
1098 #[must_use]
1100 pub fn get_node_type(&self, name: &str) -> Option<NodeTypeDefinition> {
1101 self.node_types.read().get(name).cloned()
1102 }
1103
1104 #[must_use]
1106 pub fn all_node_types(&self) -> Vec<String> {
1107 self.node_types.read().keys().cloned().collect()
1108 }
1109
1110 pub fn register_edge_type(&self, def: EdgeTypeDefinition) -> Result<(), CatalogError> {
1114 let mut types = self.edge_types.write();
1115 if types.contains_key(&def.name) {
1116 return Err(CatalogError::TypeAlreadyExists(def.name));
1117 }
1118 types.insert(def.name.clone(), def);
1119 Ok(())
1120 }
1121
1122 pub fn register_or_replace_edge_type(&self, def: EdgeTypeDefinition) {
1124 self.edge_types.write().insert(def.name.clone(), def);
1125 }
1126
1127 pub fn drop_edge_type(&self, name: &str) -> Result<(), CatalogError> {
1129 let mut types = self.edge_types.write();
1130 if types.remove(name).is_none() {
1131 return Err(CatalogError::TypeNotFound(name.to_string()));
1132 }
1133 Ok(())
1134 }
1135
1136 #[must_use]
1138 pub fn get_edge_type(&self, name: &str) -> Option<EdgeTypeDefinition> {
1139 self.edge_types.read().get(name).cloned()
1140 }
1141
1142 #[must_use]
1144 pub fn all_edge_types(&self) -> Vec<String> {
1145 self.edge_types.read().keys().cloned().collect()
1146 }
1147
1148 pub fn register_graph_type(&self, def: GraphTypeDefinition) -> Result<(), CatalogError> {
1152 let mut types = self.graph_types.write();
1153 if types.contains_key(&def.name) {
1154 return Err(CatalogError::TypeAlreadyExists(def.name));
1155 }
1156 types.insert(def.name.clone(), def);
1157 Ok(())
1158 }
1159
1160 pub fn drop_graph_type(&self, name: &str) -> Result<(), CatalogError> {
1162 let mut types = self.graph_types.write();
1163 if types.remove(name).is_none() {
1164 return Err(CatalogError::TypeNotFound(name.to_string()));
1165 }
1166 Ok(())
1167 }
1168
1169 #[must_use]
1171 pub fn get_graph_type(&self, name: &str) -> Option<GraphTypeDefinition> {
1172 self.graph_types.read().get(name).cloned()
1173 }
1174
1175 pub fn register_schema(&self, name: String) -> Result<(), CatalogError> {
1179 let mut schemas = self.schemas.write();
1180 if schemas.contains(&name) {
1181 return Err(CatalogError::SchemaAlreadyExists(name));
1182 }
1183 schemas.push(name);
1184 Ok(())
1185 }
1186
1187 pub fn drop_schema(&self, name: &str) -> Result<(), CatalogError> {
1189 let mut schemas = self.schemas.write();
1190 if let Some(pos) = schemas.iter().position(|s| s == name) {
1191 schemas.remove(pos);
1192 Ok(())
1193 } else {
1194 Err(CatalogError::SchemaNotFound(name.to_string()))
1195 }
1196 }
1197
1198 pub fn alter_node_type_add_property(
1202 &self,
1203 type_name: &str,
1204 property: TypedProperty,
1205 ) -> Result<(), CatalogError> {
1206 let mut types = self.node_types.write();
1207 let def = types
1208 .get_mut(type_name)
1209 .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1210 if def.properties.iter().any(|p| p.name == property.name) {
1211 return Err(CatalogError::TypeAlreadyExists(format!(
1212 "property {} on {}",
1213 property.name, type_name
1214 )));
1215 }
1216 def.properties.push(property);
1217 Ok(())
1218 }
1219
1220 pub fn alter_node_type_drop_property(
1222 &self,
1223 type_name: &str,
1224 property_name: &str,
1225 ) -> Result<(), CatalogError> {
1226 let mut types = self.node_types.write();
1227 let def = types
1228 .get_mut(type_name)
1229 .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1230 let len_before = def.properties.len();
1231 def.properties.retain(|p| p.name != property_name);
1232 if def.properties.len() == len_before {
1233 return Err(CatalogError::TypeNotFound(format!(
1234 "property {} on {}",
1235 property_name, type_name
1236 )));
1237 }
1238 Ok(())
1239 }
1240
1241 pub fn alter_edge_type_add_property(
1243 &self,
1244 type_name: &str,
1245 property: TypedProperty,
1246 ) -> Result<(), CatalogError> {
1247 let mut types = self.edge_types.write();
1248 let def = types
1249 .get_mut(type_name)
1250 .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1251 if def.properties.iter().any(|p| p.name == property.name) {
1252 return Err(CatalogError::TypeAlreadyExists(format!(
1253 "property {} on {}",
1254 property.name, type_name
1255 )));
1256 }
1257 def.properties.push(property);
1258 Ok(())
1259 }
1260
1261 pub fn alter_edge_type_drop_property(
1263 &self,
1264 type_name: &str,
1265 property_name: &str,
1266 ) -> Result<(), CatalogError> {
1267 let mut types = self.edge_types.write();
1268 let def = types
1269 .get_mut(type_name)
1270 .ok_or_else(|| CatalogError::TypeNotFound(type_name.to_string()))?;
1271 let len_before = def.properties.len();
1272 def.properties.retain(|p| p.name != property_name);
1273 if def.properties.len() == len_before {
1274 return Err(CatalogError::TypeNotFound(format!(
1275 "property {} on {}",
1276 property_name, type_name
1277 )));
1278 }
1279 Ok(())
1280 }
1281
1282 pub fn alter_graph_type_add_node_type(
1284 &self,
1285 graph_type_name: &str,
1286 node_type: String,
1287 ) -> Result<(), CatalogError> {
1288 let mut types = self.graph_types.write();
1289 let def = types
1290 .get_mut(graph_type_name)
1291 .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1292 if !def.allowed_node_types.contains(&node_type) {
1293 def.allowed_node_types.push(node_type);
1294 }
1295 Ok(())
1296 }
1297
1298 pub fn alter_graph_type_drop_node_type(
1300 &self,
1301 graph_type_name: &str,
1302 node_type: &str,
1303 ) -> Result<(), CatalogError> {
1304 let mut types = self.graph_types.write();
1305 let def = types
1306 .get_mut(graph_type_name)
1307 .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1308 def.allowed_node_types.retain(|t| t != node_type);
1309 Ok(())
1310 }
1311
1312 pub fn alter_graph_type_add_edge_type(
1314 &self,
1315 graph_type_name: &str,
1316 edge_type: String,
1317 ) -> Result<(), CatalogError> {
1318 let mut types = self.graph_types.write();
1319 let def = types
1320 .get_mut(graph_type_name)
1321 .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1322 if !def.allowed_edge_types.contains(&edge_type) {
1323 def.allowed_edge_types.push(edge_type);
1324 }
1325 Ok(())
1326 }
1327
1328 pub fn alter_graph_type_drop_edge_type(
1330 &self,
1331 graph_type_name: &str,
1332 edge_type: &str,
1333 ) -> Result<(), CatalogError> {
1334 let mut types = self.graph_types.write();
1335 let def = types
1336 .get_mut(graph_type_name)
1337 .ok_or_else(|| CatalogError::TypeNotFound(graph_type_name.to_string()))?;
1338 def.allowed_edge_types.retain(|t| t != edge_type);
1339 Ok(())
1340 }
1341
1342 pub fn register_procedure(&self, def: ProcedureDefinition) -> Result<(), CatalogError> {
1346 let mut procs = self.procedures.write();
1347 if procs.contains_key(&def.name) {
1348 return Err(CatalogError::TypeAlreadyExists(def.name.clone()));
1349 }
1350 procs.insert(def.name.clone(), def);
1351 Ok(())
1352 }
1353
1354 pub fn replace_procedure(&self, def: ProcedureDefinition) {
1356 self.procedures.write().insert(def.name.clone(), def);
1357 }
1358
1359 pub fn drop_procedure(&self, name: &str) -> Result<(), CatalogError> {
1361 let mut procs = self.procedures.write();
1362 if procs.remove(name).is_none() {
1363 return Err(CatalogError::TypeNotFound(name.to_string()));
1364 }
1365 Ok(())
1366 }
1367
1368 pub fn get_procedure(&self, name: &str) -> Option<ProcedureDefinition> {
1370 self.procedures.read().get(name).cloned()
1371 }
1372
1373 fn add_unique_constraint(
1374 &self,
1375 label: LabelId,
1376 property_key: PropertyKeyId,
1377 ) -> Result<(), CatalogError> {
1378 let mut constraints = self.unique_constraints.write();
1379 let key = (label, property_key);
1380 if !constraints.insert(key) {
1381 return Err(CatalogError::ConstraintAlreadyExists);
1382 }
1383 Ok(())
1384 }
1385
1386 fn add_required_property(
1387 &self,
1388 label: LabelId,
1389 property_key: PropertyKeyId,
1390 ) -> Result<(), CatalogError> {
1391 let mut required = self.required_properties.write();
1392 let key = (label, property_key);
1393 if !required.insert(key) {
1394 return Err(CatalogError::ConstraintAlreadyExists);
1395 }
1396 Ok(())
1397 }
1398
1399 fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1400 self.required_properties
1401 .read()
1402 .contains(&(label, property_key))
1403 }
1404
1405 fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
1406 self.unique_constraints
1407 .read()
1408 .contains(&(label, property_key))
1409 }
1410}
1411
1412#[derive(Debug, Clone, PartialEq, Eq)]
1416pub enum CatalogError {
1417 SchemaNotEnabled,
1419 ConstraintAlreadyExists,
1421 LabelNotFound(String),
1423 PropertyKeyNotFound(String),
1425 EdgeTypeNotFound(String),
1427 IndexNotFound(IndexId),
1429 TypeAlreadyExists(String),
1431 TypeNotFound(String),
1433 SchemaAlreadyExists(String),
1435 SchemaNotFound(String),
1437}
1438
1439impl std::fmt::Display for CatalogError {
1440 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1441 match self {
1442 Self::SchemaNotEnabled => write!(f, "Schema constraints are not enabled"),
1443 Self::ConstraintAlreadyExists => write!(f, "Constraint already exists"),
1444 Self::LabelNotFound(name) => write!(f, "Label not found: {name}"),
1445 Self::PropertyKeyNotFound(name) => write!(f, "Property key not found: {name}"),
1446 Self::EdgeTypeNotFound(name) => write!(f, "Edge type not found: {name}"),
1447 Self::IndexNotFound(id) => write!(f, "Index not found: {id}"),
1448 Self::TypeAlreadyExists(name) => write!(f, "Type already exists: {name}"),
1449 Self::TypeNotFound(name) => write!(f, "Type not found: {name}"),
1450 Self::SchemaAlreadyExists(name) => write!(f, "Schema already exists: {name}"),
1451 Self::SchemaNotFound(name) => write!(f, "Schema not found: {name}"),
1452 }
1453 }
1454}
1455
1456impl std::error::Error for CatalogError {}
1457
1458use grafeo_core::execution::operators::ConstraintValidator;
1461use grafeo_core::execution::operators::OperatorError;
1462
1463pub struct CatalogConstraintValidator {
1468 catalog: Arc<Catalog>,
1469}
1470
1471impl CatalogConstraintValidator {
1472 pub fn new(catalog: Arc<Catalog>) -> Self {
1474 Self { catalog }
1475 }
1476}
1477
1478impl ConstraintValidator for CatalogConstraintValidator {
1479 fn validate_node_property(
1480 &self,
1481 labels: &[String],
1482 key: &str,
1483 value: &Value,
1484 ) -> Result<(), OperatorError> {
1485 for label in labels {
1486 if let Some(type_def) = self.catalog.get_node_type(label)
1487 && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1488 {
1489 if !typed_prop.nullable && *value == Value::Null {
1491 return Err(OperatorError::ConstraintViolation(format!(
1492 "property '{key}' on :{label} is NOT NULL, cannot set to null"
1493 )));
1494 }
1495 if *value != Value::Null && !typed_prop.data_type.matches(value) {
1497 return Err(OperatorError::ConstraintViolation(format!(
1498 "property '{key}' on :{label} expects {:?}, got {:?}",
1499 typed_prop.data_type, value
1500 )));
1501 }
1502 }
1503 }
1504 Ok(())
1505 }
1506
1507 fn validate_node_complete(
1508 &self,
1509 labels: &[String],
1510 properties: &[(String, Value)],
1511 ) -> Result<(), OperatorError> {
1512 let prop_names: std::collections::HashSet<&str> =
1513 properties.iter().map(|(n, _)| n.as_str()).collect();
1514
1515 for label in labels {
1516 if let Some(type_def) = self.catalog.get_node_type(label) {
1517 for typed_prop in &type_def.properties {
1519 if !typed_prop.nullable
1520 && typed_prop.default_value.is_none()
1521 && !prop_names.contains(typed_prop.name.as_str())
1522 {
1523 return Err(OperatorError::ConstraintViolation(format!(
1524 "missing required property '{}' on :{label}",
1525 typed_prop.name
1526 )));
1527 }
1528 }
1529 for constraint in &type_def.constraints {
1531 match constraint {
1532 TypeConstraint::NotNull(prop_name) => {
1533 if !prop_names.contains(prop_name.as_str()) {
1534 return Err(OperatorError::ConstraintViolation(format!(
1535 "missing required property '{prop_name}' on :{label} (NOT NULL constraint)"
1536 )));
1537 }
1538 }
1539 TypeConstraint::PrimaryKey(key_props) => {
1540 for pk in key_props {
1541 if !prop_names.contains(pk.as_str()) {
1542 return Err(OperatorError::ConstraintViolation(format!(
1543 "missing primary key property '{pk}' on :{label}"
1544 )));
1545 }
1546 }
1547 }
1548 _ => {}
1549 }
1550 }
1551 }
1552 }
1553 Ok(())
1554 }
1555
1556 fn check_unique_node_property(
1557 &self,
1558 labels: &[String],
1559 key: &str,
1560 value: &Value,
1561 ) -> Result<(), OperatorError> {
1562 if *value == Value::Null {
1564 return Ok(());
1565 }
1566 for label in labels {
1567 if let Some(type_def) = self.catalog.get_node_type(label) {
1568 for constraint in &type_def.constraints {
1569 let is_unique = match constraint {
1570 TypeConstraint::Unique(props) => props.iter().any(|p| p == key),
1571 TypeConstraint::PrimaryKey(props) => props.iter().any(|p| p == key),
1572 _ => false,
1573 };
1574 if is_unique {
1575 let label_id = self.catalog.get_or_create_label(label);
1577 let prop_id = self.catalog.get_or_create_property_key(key);
1578 if self.catalog.is_property_unique(label_id, prop_id) {
1579 }
1585 }
1586 }
1587 }
1588 }
1589 Ok(())
1590 }
1591
1592 fn validate_edge_property(
1593 &self,
1594 edge_type: &str,
1595 key: &str,
1596 value: &Value,
1597 ) -> Result<(), OperatorError> {
1598 if let Some(type_def) = self.catalog.get_edge_type_def(edge_type)
1599 && let Some(typed_prop) = type_def.properties.iter().find(|p| p.name == key)
1600 {
1601 if !typed_prop.nullable && *value == Value::Null {
1603 return Err(OperatorError::ConstraintViolation(format!(
1604 "property '{key}' on :{edge_type} is NOT NULL, cannot set to null"
1605 )));
1606 }
1607 if *value != Value::Null && !typed_prop.data_type.matches(value) {
1609 return Err(OperatorError::ConstraintViolation(format!(
1610 "property '{key}' on :{edge_type} expects {:?}, got {:?}",
1611 typed_prop.data_type, value
1612 )));
1613 }
1614 }
1615 Ok(())
1616 }
1617
1618 fn validate_edge_complete(
1619 &self,
1620 edge_type: &str,
1621 properties: &[(String, Value)],
1622 ) -> Result<(), OperatorError> {
1623 if let Some(type_def) = self.catalog.get_edge_type_def(edge_type) {
1624 let prop_names: std::collections::HashSet<&str> =
1625 properties.iter().map(|(n, _)| n.as_str()).collect();
1626
1627 for typed_prop in &type_def.properties {
1628 if !typed_prop.nullable
1629 && typed_prop.default_value.is_none()
1630 && !prop_names.contains(typed_prop.name.as_str())
1631 {
1632 return Err(OperatorError::ConstraintViolation(format!(
1633 "missing required property '{}' on :{edge_type}",
1634 typed_prop.name
1635 )));
1636 }
1637 }
1638 }
1639 Ok(())
1640 }
1641}
1642
1643#[cfg(test)]
1644mod tests {
1645 use super::*;
1646 use std::thread;
1647
1648 #[test]
1649 fn test_catalog_labels() {
1650 let catalog = Catalog::new();
1651
1652 let person_id = catalog.get_or_create_label("Person");
1654 let company_id = catalog.get_or_create_label("Company");
1655
1656 assert_ne!(person_id, company_id);
1658
1659 assert_eq!(catalog.get_or_create_label("Person"), person_id);
1661
1662 assert_eq!(catalog.get_label_id("Person"), Some(person_id));
1664 assert_eq!(catalog.get_label_id("Company"), Some(company_id));
1665 assert_eq!(catalog.get_label_id("Unknown"), None);
1666
1667 assert_eq!(catalog.get_label_name(person_id).as_deref(), Some("Person"));
1669 assert_eq!(
1670 catalog.get_label_name(company_id).as_deref(),
1671 Some("Company")
1672 );
1673
1674 assert_eq!(catalog.label_count(), 2);
1676 }
1677
1678 #[test]
1679 fn test_catalog_property_keys() {
1680 let catalog = Catalog::new();
1681
1682 let name_id = catalog.get_or_create_property_key("name");
1683 let age_id = catalog.get_or_create_property_key("age");
1684
1685 assert_ne!(name_id, age_id);
1686 assert_eq!(catalog.get_or_create_property_key("name"), name_id);
1687 assert_eq!(catalog.get_property_key_id("name"), Some(name_id));
1688 assert_eq!(
1689 catalog.get_property_key_name(name_id).as_deref(),
1690 Some("name")
1691 );
1692 assert_eq!(catalog.property_key_count(), 2);
1693 }
1694
1695 #[test]
1696 fn test_catalog_edge_types() {
1697 let catalog = Catalog::new();
1698
1699 let knows_id = catalog.get_or_create_edge_type("KNOWS");
1700 let works_at_id = catalog.get_or_create_edge_type("WORKS_AT");
1701
1702 assert_ne!(knows_id, works_at_id);
1703 assert_eq!(catalog.get_or_create_edge_type("KNOWS"), knows_id);
1704 assert_eq!(catalog.get_edge_type_id("KNOWS"), Some(knows_id));
1705 assert_eq!(
1706 catalog.get_edge_type_name(knows_id).as_deref(),
1707 Some("KNOWS")
1708 );
1709 assert_eq!(catalog.edge_type_count(), 2);
1710 }
1711
1712 #[test]
1713 fn test_catalog_indexes() {
1714 let catalog = Catalog::new();
1715
1716 let person_id = catalog.get_or_create_label("Person");
1717 let name_id = catalog.get_or_create_property_key("name");
1718 let age_id = catalog.get_or_create_property_key("age");
1719
1720 let idx1 = catalog.create_index(person_id, name_id, IndexType::Hash);
1722 let idx2 = catalog.create_index(person_id, age_id, IndexType::BTree);
1723
1724 assert_ne!(idx1, idx2);
1725 assert_eq!(catalog.index_count(), 2);
1726
1727 let label_indexes = catalog.indexes_for_label(person_id);
1729 assert_eq!(label_indexes.len(), 2);
1730 assert!(label_indexes.contains(&idx1));
1731 assert!(label_indexes.contains(&idx2));
1732
1733 let name_indexes = catalog.indexes_for_label_property(person_id, name_id);
1735 assert_eq!(name_indexes.len(), 1);
1736 assert_eq!(name_indexes[0], idx1);
1737
1738 let def = catalog.get_index(idx1).unwrap();
1740 assert_eq!(def.label, person_id);
1741 assert_eq!(def.property_key, name_id);
1742 assert_eq!(def.index_type, IndexType::Hash);
1743
1744 assert!(catalog.drop_index(idx1));
1746 assert_eq!(catalog.index_count(), 1);
1747 assert!(catalog.get_index(idx1).is_none());
1748 assert_eq!(catalog.indexes_for_label(person_id).len(), 1);
1749 }
1750
1751 #[test]
1752 fn test_catalog_schema_constraints() {
1753 let catalog = Catalog::with_schema();
1754
1755 let person_id = catalog.get_or_create_label("Person");
1756 let email_id = catalog.get_or_create_property_key("email");
1757 let name_id = catalog.get_or_create_property_key("name");
1758
1759 assert!(catalog.add_unique_constraint(person_id, email_id).is_ok());
1761 assert!(catalog.add_required_property(person_id, name_id).is_ok());
1762
1763 assert!(catalog.is_property_unique(person_id, email_id));
1765 assert!(!catalog.is_property_unique(person_id, name_id));
1766 assert!(catalog.is_property_required(person_id, name_id));
1767 assert!(!catalog.is_property_required(person_id, email_id));
1768
1769 assert_eq!(
1771 catalog.add_unique_constraint(person_id, email_id),
1772 Err(CatalogError::ConstraintAlreadyExists)
1773 );
1774 }
1775
1776 #[test]
1777 fn test_catalog_schema_always_enabled() {
1778 let catalog = Catalog::new();
1780 assert!(catalog.has_schema());
1781
1782 let person_id = catalog.get_or_create_label("Person");
1783 let email_id = catalog.get_or_create_property_key("email");
1784
1785 assert_eq!(catalog.add_unique_constraint(person_id, email_id), Ok(()));
1787 }
1788
1789 #[test]
1792 fn test_catalog_default() {
1793 let catalog = Catalog::default();
1794 assert!(catalog.has_schema());
1795 assert_eq!(catalog.label_count(), 0);
1796 assert_eq!(catalog.property_key_count(), 0);
1797 assert_eq!(catalog.edge_type_count(), 0);
1798 assert_eq!(catalog.index_count(), 0);
1799 }
1800
1801 #[test]
1802 fn test_catalog_all_labels() {
1803 let catalog = Catalog::new();
1804
1805 catalog.get_or_create_label("Person");
1806 catalog.get_or_create_label("Company");
1807 catalog.get_or_create_label("Product");
1808
1809 let all = catalog.all_labels();
1810 assert_eq!(all.len(), 3);
1811 assert!(all.iter().any(|l| l.as_ref() == "Person"));
1812 assert!(all.iter().any(|l| l.as_ref() == "Company"));
1813 assert!(all.iter().any(|l| l.as_ref() == "Product"));
1814 }
1815
1816 #[test]
1817 fn test_catalog_all_property_keys() {
1818 let catalog = Catalog::new();
1819
1820 catalog.get_or_create_property_key("name");
1821 catalog.get_or_create_property_key("age");
1822 catalog.get_or_create_property_key("email");
1823
1824 let all = catalog.all_property_keys();
1825 assert_eq!(all.len(), 3);
1826 assert!(all.iter().any(|k| k.as_ref() == "name"));
1827 assert!(all.iter().any(|k| k.as_ref() == "age"));
1828 assert!(all.iter().any(|k| k.as_ref() == "email"));
1829 }
1830
1831 #[test]
1832 fn test_catalog_all_edge_types() {
1833 let catalog = Catalog::new();
1834
1835 catalog.get_or_create_edge_type("KNOWS");
1836 catalog.get_or_create_edge_type("WORKS_AT");
1837 catalog.get_or_create_edge_type("LIVES_IN");
1838
1839 let all = catalog.all_edge_types();
1840 assert_eq!(all.len(), 3);
1841 assert!(all.iter().any(|t| t.as_ref() == "KNOWS"));
1842 assert!(all.iter().any(|t| t.as_ref() == "WORKS_AT"));
1843 assert!(all.iter().any(|t| t.as_ref() == "LIVES_IN"));
1844 }
1845
1846 #[test]
1847 fn test_catalog_invalid_id_lookup() {
1848 let catalog = Catalog::new();
1849
1850 let _ = catalog.get_or_create_label("Person");
1852
1853 let invalid_label = LabelId::new(999);
1855 let invalid_property = PropertyKeyId::new(999);
1856 let invalid_edge_type = EdgeTypeId::new(999);
1857 let invalid_index = IndexId::new(999);
1858
1859 assert!(catalog.get_label_name(invalid_label).is_none());
1860 assert!(catalog.get_property_key_name(invalid_property).is_none());
1861 assert!(catalog.get_edge_type_name(invalid_edge_type).is_none());
1862 assert!(catalog.get_index(invalid_index).is_none());
1863 }
1864
1865 #[test]
1866 fn test_catalog_drop_nonexistent_index() {
1867 let catalog = Catalog::new();
1868 let invalid_index = IndexId::new(999);
1869 assert!(!catalog.drop_index(invalid_index));
1870 }
1871
1872 #[test]
1873 fn test_catalog_indexes_for_nonexistent_label() {
1874 let catalog = Catalog::new();
1875 let invalid_label = LabelId::new(999);
1876 let invalid_property = PropertyKeyId::new(999);
1877
1878 assert!(catalog.indexes_for_label(invalid_label).is_empty());
1879 assert!(
1880 catalog
1881 .indexes_for_label_property(invalid_label, invalid_property)
1882 .is_empty()
1883 );
1884 }
1885
1886 #[test]
1887 fn test_catalog_multiple_indexes_same_property() {
1888 let catalog = Catalog::new();
1889
1890 let person_id = catalog.get_or_create_label("Person");
1891 let name_id = catalog.get_or_create_property_key("name");
1892
1893 let hash_idx = catalog.create_index(person_id, name_id, IndexType::Hash);
1895 let btree_idx = catalog.create_index(person_id, name_id, IndexType::BTree);
1896 let fulltext_idx = catalog.create_index(person_id, name_id, IndexType::FullText);
1897
1898 assert_eq!(catalog.index_count(), 3);
1899
1900 let indexes = catalog.indexes_for_label_property(person_id, name_id);
1901 assert_eq!(indexes.len(), 3);
1902 assert!(indexes.contains(&hash_idx));
1903 assert!(indexes.contains(&btree_idx));
1904 assert!(indexes.contains(&fulltext_idx));
1905
1906 assert_eq!(
1908 catalog.get_index(hash_idx).unwrap().index_type,
1909 IndexType::Hash
1910 );
1911 assert_eq!(
1912 catalog.get_index(btree_idx).unwrap().index_type,
1913 IndexType::BTree
1914 );
1915 assert_eq!(
1916 catalog.get_index(fulltext_idx).unwrap().index_type,
1917 IndexType::FullText
1918 );
1919 }
1920
1921 #[test]
1922 fn test_catalog_schema_required_property_duplicate() {
1923 let catalog = Catalog::with_schema();
1924
1925 let person_id = catalog.get_or_create_label("Person");
1926 let name_id = catalog.get_or_create_property_key("name");
1927
1928 assert!(catalog.add_required_property(person_id, name_id).is_ok());
1930
1931 assert_eq!(
1933 catalog.add_required_property(person_id, name_id),
1934 Err(CatalogError::ConstraintAlreadyExists)
1935 );
1936 }
1937
1938 #[test]
1939 fn test_catalog_schema_check_without_constraints() {
1940 let catalog = Catalog::new();
1941
1942 let person_id = catalog.get_or_create_label("Person");
1943 let name_id = catalog.get_or_create_property_key("name");
1944
1945 assert!(!catalog.is_property_unique(person_id, name_id));
1947 assert!(!catalog.is_property_required(person_id, name_id));
1948 }
1949
1950 #[test]
1951 fn test_catalog_has_schema() {
1952 let catalog = Catalog::new();
1954 assert!(catalog.has_schema());
1955
1956 let with_schema = Catalog::with_schema();
1957 assert!(with_schema.has_schema());
1958 }
1959
1960 #[test]
1961 fn test_catalog_error_display() {
1962 assert_eq!(
1963 CatalogError::SchemaNotEnabled.to_string(),
1964 "Schema constraints are not enabled"
1965 );
1966 assert_eq!(
1967 CatalogError::ConstraintAlreadyExists.to_string(),
1968 "Constraint already exists"
1969 );
1970 assert_eq!(
1971 CatalogError::LabelNotFound("Person".to_string()).to_string(),
1972 "Label not found: Person"
1973 );
1974 assert_eq!(
1975 CatalogError::PropertyKeyNotFound("name".to_string()).to_string(),
1976 "Property key not found: name"
1977 );
1978 assert_eq!(
1979 CatalogError::EdgeTypeNotFound("KNOWS".to_string()).to_string(),
1980 "Edge type not found: KNOWS"
1981 );
1982 let idx = IndexId::new(42);
1983 assert!(CatalogError::IndexNotFound(idx).to_string().contains("42"));
1984 }
1985
1986 #[test]
1987 fn test_catalog_concurrent_label_creation() {
1988 use std::sync::Arc;
1989
1990 let catalog = Arc::new(Catalog::new());
1991 let mut handles = vec![];
1992
1993 for i in 0..10 {
1995 let catalog = Arc::clone(&catalog);
1996 handles.push(thread::spawn(move || {
1997 let label_name = format!("Label{}", i % 3); catalog.get_or_create_label(&label_name)
1999 }));
2000 }
2001
2002 let mut ids: Vec<LabelId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2003 ids.sort_by_key(|id| id.as_u32());
2004 ids.dedup();
2005
2006 assert_eq!(ids.len(), 3);
2008 assert_eq!(catalog.label_count(), 3);
2009 }
2010
2011 #[test]
2012 fn test_catalog_concurrent_property_key_creation() {
2013 use std::sync::Arc;
2014
2015 let catalog = Arc::new(Catalog::new());
2016 let mut handles = vec![];
2017
2018 for i in 0..10 {
2019 let catalog = Arc::clone(&catalog);
2020 handles.push(thread::spawn(move || {
2021 let key_name = format!("key{}", i % 4);
2022 catalog.get_or_create_property_key(&key_name)
2023 }));
2024 }
2025
2026 let mut ids: Vec<PropertyKeyId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2027 ids.sort_by_key(|id| id.as_u32());
2028 ids.dedup();
2029
2030 assert_eq!(ids.len(), 4);
2031 assert_eq!(catalog.property_key_count(), 4);
2032 }
2033
2034 #[test]
2035 fn test_catalog_concurrent_index_operations() {
2036 use std::sync::Arc;
2037
2038 let catalog = Arc::new(Catalog::new());
2039 let label = catalog.get_or_create_label("Node");
2040
2041 let mut handles = vec![];
2042
2043 for i in 0..5 {
2045 let catalog = Arc::clone(&catalog);
2046 handles.push(thread::spawn(move || {
2047 let prop = PropertyKeyId::new(i);
2048 catalog.create_index(label, prop, IndexType::Hash)
2049 }));
2050 }
2051
2052 let ids: Vec<IndexId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
2053 assert_eq!(ids.len(), 5);
2054 assert_eq!(catalog.index_count(), 5);
2055 }
2056
2057 #[test]
2058 fn test_catalog_special_characters_in_names() {
2059 let catalog = Catalog::new();
2060
2061 let label1 = catalog.get_or_create_label("Label With Spaces");
2063 let label2 = catalog.get_or_create_label("Label-With-Dashes");
2064 let label3 = catalog.get_or_create_label("Label_With_Underscores");
2065 let label4 = catalog.get_or_create_label("LabelWithUnicode\u{00E9}");
2066
2067 assert_ne!(label1, label2);
2068 assert_ne!(label2, label3);
2069 assert_ne!(label3, label4);
2070
2071 assert_eq!(
2072 catalog.get_label_name(label1).as_deref(),
2073 Some("Label With Spaces")
2074 );
2075 assert_eq!(
2076 catalog.get_label_name(label4).as_deref(),
2077 Some("LabelWithUnicode\u{00E9}")
2078 );
2079 }
2080
2081 #[test]
2082 fn test_catalog_empty_names() {
2083 let catalog = Catalog::new();
2084
2085 let empty_label = catalog.get_or_create_label("");
2087 let empty_prop = catalog.get_or_create_property_key("");
2088 let empty_edge = catalog.get_or_create_edge_type("");
2089
2090 assert_eq!(catalog.get_label_name(empty_label).as_deref(), Some(""));
2091 assert_eq!(
2092 catalog.get_property_key_name(empty_prop).as_deref(),
2093 Some("")
2094 );
2095 assert_eq!(catalog.get_edge_type_name(empty_edge).as_deref(), Some(""));
2096
2097 assert_eq!(catalog.get_or_create_label(""), empty_label);
2099 }
2100
2101 #[test]
2102 fn test_catalog_large_number_of_entries() {
2103 let catalog = Catalog::new();
2104
2105 for i in 0..1000 {
2107 catalog.get_or_create_label(&format!("Label{}", i));
2108 }
2109
2110 assert_eq!(catalog.label_count(), 1000);
2111
2112 let all = catalog.all_labels();
2114 assert_eq!(all.len(), 1000);
2115
2116 let id = catalog.get_label_id("Label500").unwrap();
2118 assert_eq!(catalog.get_label_name(id).as_deref(), Some("Label500"));
2119 }
2120
2121 #[test]
2122 fn test_index_definition_debug() {
2123 let def = IndexDefinition {
2124 id: IndexId::new(1),
2125 label: LabelId::new(2),
2126 property_key: PropertyKeyId::new(3),
2127 index_type: IndexType::Hash,
2128 };
2129
2130 let debug_str = format!("{:?}", def);
2132 assert!(debug_str.contains("IndexDefinition"));
2133 assert!(debug_str.contains("Hash"));
2134 }
2135
2136 #[test]
2137 fn test_index_type_equality() {
2138 assert_eq!(IndexType::Hash, IndexType::Hash);
2139 assert_ne!(IndexType::Hash, IndexType::BTree);
2140 assert_ne!(IndexType::BTree, IndexType::FullText);
2141
2142 let t = IndexType::Hash;
2144 let t2 = t;
2145 assert_eq!(t, t2);
2146 }
2147
2148 #[test]
2149 fn test_catalog_error_equality() {
2150 assert_eq!(
2151 CatalogError::SchemaNotEnabled,
2152 CatalogError::SchemaNotEnabled
2153 );
2154 assert_eq!(
2155 CatalogError::ConstraintAlreadyExists,
2156 CatalogError::ConstraintAlreadyExists
2157 );
2158 assert_eq!(
2159 CatalogError::LabelNotFound("X".to_string()),
2160 CatalogError::LabelNotFound("X".to_string())
2161 );
2162 assert_ne!(
2163 CatalogError::LabelNotFound("X".to_string()),
2164 CatalogError::LabelNotFound("Y".to_string())
2165 );
2166 }
2167}