1use std::fmt;
4use std::path::PathBuf;
5use std::time::Duration;
6
7#[cfg(feature = "encryption")]
16#[derive(Clone)]
17pub struct EncryptionConfig {
18 pub key_chain: std::sync::Arc<grafeo_common::encryption::KeyChain>,
21}
22
23#[cfg(feature = "encryption")]
24impl fmt::Debug for EncryptionConfig {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.debug_struct("EncryptionConfig")
27 .field("key_chain", &"[redacted]")
28 .finish()
29 }
30}
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
40#[non_exhaustive]
41pub enum GraphModel {
42 #[default]
44 Lpg,
45 Rdf,
47}
48
49impl fmt::Display for GraphModel {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 Self::Lpg => write!(f, "LPG"),
53 Self::Rdf => write!(f, "RDF"),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
65#[non_exhaustive]
66pub enum AccessMode {
67 #[default]
69 ReadWrite,
70 ReadOnly,
74}
75
76impl fmt::Display for AccessMode {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match self {
79 Self::ReadWrite => write!(f, "read-write"),
80 Self::ReadOnly => write!(f, "read-only"),
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
91#[non_exhaustive]
92pub enum StorageFormat {
93 #[default]
96 Auto,
97 WalDirectory,
99 SingleFile,
102}
103
104impl fmt::Display for StorageFormat {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self {
107 Self::Auto => write!(f, "auto"),
108 Self::WalDirectory => write!(f, "wal-directory"),
109 Self::SingleFile => write!(f, "single-file"),
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120#[non_exhaustive]
121pub enum DurabilityMode {
122 Sync,
124 Batch {
126 max_delay_ms: u64,
128 max_records: u64,
130 },
131 Adaptive {
133 target_interval_ms: u64,
135 },
136 NoSync,
138}
139
140impl Default for DurabilityMode {
141 fn default() -> Self {
142 Self::Batch {
143 max_delay_ms: 100,
144 max_records: 1000,
145 }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151#[non_exhaustive]
152pub enum ConfigError {
153 ZeroMemoryLimit,
155 ZeroThreads,
157 ZeroWalFlushInterval,
159 RdfFeatureRequired,
161}
162
163impl fmt::Display for ConfigError {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 Self::ZeroMemoryLimit => write!(f, "memory_limit must be greater than zero"),
167 Self::ZeroThreads => write!(f, "threads must be greater than zero"),
168 Self::ZeroWalFlushInterval => {
169 write!(f, "wal_flush_interval_ms must be greater than zero")
170 }
171 Self::RdfFeatureRequired => {
172 write!(
173 f,
174 "RDF graph model requires the `rdf` feature flag to be enabled"
175 )
176 }
177 }
178 }
179}
180
181impl std::error::Error for ConfigError {}
182
183#[derive(Debug, Clone)]
185#[allow(clippy::struct_excessive_bools)] pub struct Config {
187 pub graph_model: GraphModel,
189 pub path: Option<PathBuf>,
191
192 pub memory_limit: Option<usize>,
194
195 pub spill_path: Option<PathBuf>,
197
198 pub threads: usize,
200
201 pub wal_enabled: bool,
203
204 pub wal_flush_interval_ms: u64,
206
207 pub backward_edges: bool,
209
210 pub query_logging: bool,
212
213 pub adaptive: AdaptiveConfig,
215
216 pub factorized_execution: bool,
224
225 pub wal_durability: DurabilityMode,
227
228 pub storage_format: StorageFormat,
233
234 pub schema_constraints: bool,
240
241 pub query_timeout: Option<Duration>,
250
251 pub max_property_size: Option<usize>,
260
261 pub gc_interval: usize,
266
267 pub access_mode: AccessMode,
273
274 pub cdc_enabled: bool,
285
286 #[cfg(feature = "cdc")]
293 pub cdc_retention: crate::cdc::CdcRetentionConfig,
294
295 pub section_configs: hashbrown::HashMap<
301 grafeo_common::storage::SectionType,
302 grafeo_common::storage::SectionMemoryConfig,
303 >,
304
305 pub checkpoint_interval: Option<Duration>,
311
312 #[cfg(feature = "encryption")]
320 pub encryption: Option<EncryptionConfig>,
321}
322
323#[derive(Debug, Clone)]
328pub struct AdaptiveConfig {
329 pub enabled: bool,
331
332 pub threshold: f64,
337
338 pub min_rows: u64,
342
343 pub max_reoptimizations: usize,
345}
346
347impl Default for AdaptiveConfig {
348 fn default() -> Self {
349 Self {
350 enabled: true,
351 threshold: 3.0,
352 min_rows: 1000,
353 max_reoptimizations: 3,
354 }
355 }
356}
357
358impl AdaptiveConfig {
359 #[must_use]
361 pub fn disabled() -> Self {
362 Self {
363 enabled: false,
364 ..Default::default()
365 }
366 }
367
368 #[must_use]
370 pub fn with_threshold(mut self, threshold: f64) -> Self {
371 self.threshold = threshold;
372 self
373 }
374
375 #[must_use]
377 pub fn with_min_rows(mut self, min_rows: u64) -> Self {
378 self.min_rows = min_rows;
379 self
380 }
381
382 #[must_use]
384 pub fn with_max_reoptimizations(mut self, max: usize) -> Self {
385 self.max_reoptimizations = max;
386 self
387 }
388}
389
390impl Default for Config {
391 fn default() -> Self {
392 Self {
393 graph_model: GraphModel::default(),
394 path: None,
395 memory_limit: None,
396 spill_path: None,
397 threads: num_cpus::get(),
398 wal_enabled: true,
399 wal_flush_interval_ms: 100,
400 backward_edges: true,
401 query_logging: false,
402 adaptive: AdaptiveConfig::default(),
403 factorized_execution: true,
404 wal_durability: DurabilityMode::default(),
405 storage_format: StorageFormat::default(),
406 schema_constraints: false,
407 query_timeout: Some(Duration::from_secs(30)),
408 max_property_size: Some(16 * 1024 * 1024), gc_interval: 100,
410 access_mode: AccessMode::default(),
411 cdc_enabled: false,
412 #[cfg(feature = "cdc")]
413 cdc_retention: crate::cdc::CdcRetentionConfig::default(),
414 section_configs: hashbrown::HashMap::new(),
415 checkpoint_interval: None,
416 #[cfg(feature = "encryption")]
417 encryption: None,
418 }
419 }
420}
421
422impl Config {
423 #[must_use]
425 pub fn in_memory() -> Self {
426 Self {
427 path: None,
428 wal_enabled: false,
429 ..Default::default()
430 }
431 }
432
433 #[must_use]
435 pub fn persistent(path: impl Into<PathBuf>) -> Self {
436 Self {
437 path: Some(path.into()),
438 wal_enabled: true,
439 ..Default::default()
440 }
441 }
442
443 #[must_use]
445 pub fn with_memory_limit(mut self, limit: usize) -> Self {
446 self.memory_limit = Some(limit);
447 self
448 }
449
450 #[must_use]
452 pub fn with_threads(mut self, threads: usize) -> Self {
453 self.threads = threads;
454 self
455 }
456
457 #[must_use]
459 pub fn without_backward_edges(mut self) -> Self {
460 self.backward_edges = false;
461 self
462 }
463
464 #[must_use]
466 pub fn with_query_logging(mut self) -> Self {
467 self.query_logging = true;
468 self
469 }
470
471 #[must_use]
473 pub fn with_memory_fraction(mut self, fraction: f64) -> Self {
474 use grafeo_common::memory::buffer::BufferManagerConfig;
475 let system_memory = BufferManagerConfig::detect_system_memory();
476 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
478 let budget = (system_memory as f64 * fraction) as usize;
479 self.memory_limit = Some(budget);
480 self
481 }
482
483 #[must_use]
485 pub fn with_spill_path(mut self, path: impl Into<PathBuf>) -> Self {
486 self.spill_path = Some(path.into());
487 self
488 }
489
490 #[must_use]
492 pub fn with_adaptive(mut self, adaptive: AdaptiveConfig) -> Self {
493 self.adaptive = adaptive;
494 self
495 }
496
497 #[must_use]
499 pub fn without_adaptive(mut self) -> Self {
500 self.adaptive.enabled = false;
501 self
502 }
503
504 #[must_use]
510 pub fn without_factorized_execution(mut self) -> Self {
511 self.factorized_execution = false;
512 self
513 }
514
515 #[must_use]
517 pub fn with_graph_model(mut self, model: GraphModel) -> Self {
518 self.graph_model = model;
519 self
520 }
521
522 #[must_use]
524 pub fn with_wal_durability(mut self, mode: DurabilityMode) -> Self {
525 self.wal_durability = mode;
526 self
527 }
528
529 #[must_use]
531 pub fn with_storage_format(mut self, format: StorageFormat) -> Self {
532 self.storage_format = format;
533 self
534 }
535
536 #[must_use]
538 pub fn with_schema_constraints(mut self) -> Self {
539 self.schema_constraints = true;
540 self
541 }
542
543 #[must_use]
545 pub fn with_query_timeout(mut self, timeout: Duration) -> Self {
546 self.query_timeout = Some(timeout);
547 self
548 }
549
550 #[must_use]
552 pub fn without_query_timeout(mut self) -> Self {
553 self.query_timeout = None;
554 self
555 }
556
557 #[must_use]
559 pub fn with_max_property_size(mut self, size: usize) -> Self {
560 self.max_property_size = Some(size);
561 self
562 }
563
564 #[must_use]
566 pub fn without_max_property_size(mut self) -> Self {
567 self.max_property_size = None;
568 self
569 }
570
571 #[must_use]
575 pub fn with_gc_interval(mut self, interval: usize) -> Self {
576 self.gc_interval = interval;
577 self
578 }
579
580 #[must_use]
582 pub fn with_access_mode(mut self, mode: AccessMode) -> Self {
583 self.access_mode = mode;
584 self
585 }
586
587 #[must_use]
592 pub fn read_only(path: impl Into<PathBuf>) -> Self {
593 Self {
594 path: Some(path.into()),
595 wal_enabled: false,
596 access_mode: AccessMode::ReadOnly,
597 ..Default::default()
598 }
599 }
600
601 #[must_use]
609 pub fn with_cdc(mut self) -> Self {
610 self.cdc_enabled = true;
611 self
612 }
613
614 #[must_use]
632 pub fn with_section_config(
633 mut self,
634 section_type: grafeo_common::storage::SectionType,
635 config: grafeo_common::storage::SectionMemoryConfig,
636 ) -> Self {
637 self.section_configs.insert(section_type, config);
638 self
639 }
640
641 #[must_use]
646 pub fn with_checkpoint_interval(mut self, interval: Duration) -> Self {
647 self.checkpoint_interval = Some(interval);
648 self
649 }
650
651 pub fn validate(&self) -> std::result::Result<(), ConfigError> {
659 if let Some(limit) = self.memory_limit
660 && limit == 0
661 {
662 return Err(ConfigError::ZeroMemoryLimit);
663 }
664
665 if self.threads == 0 {
666 return Err(ConfigError::ZeroThreads);
667 }
668
669 if self.wal_flush_interval_ms == 0 {
670 return Err(ConfigError::ZeroWalFlushInterval);
671 }
672
673 #[cfg(not(feature = "triple-store"))]
674 if self.graph_model == GraphModel::Rdf {
675 return Err(ConfigError::RdfFeatureRequired);
676 }
677
678 Ok(())
679 }
680}
681
682mod num_cpus {
684 #[cfg(not(target_arch = "wasm32"))]
685 pub fn get() -> usize {
686 std::thread::available_parallelism().map_or(4, |n| n.get())
687 }
688
689 #[cfg(target_arch = "wasm32")]
690 pub fn get() -> usize {
691 1
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 #[test]
700 fn test_config_default() {
701 let config = Config::default();
702 assert_eq!(config.graph_model, GraphModel::Lpg);
703 assert!(config.path.is_none());
704 assert!(config.memory_limit.is_none());
705 assert!(config.spill_path.is_none());
706 assert!(config.threads > 0);
707 assert!(config.wal_enabled);
708 assert_eq!(config.wal_flush_interval_ms, 100);
709 assert!(config.backward_edges);
710 assert!(!config.query_logging);
711 assert!(config.factorized_execution);
712 assert_eq!(config.wal_durability, DurabilityMode::default());
713 assert!(!config.schema_constraints);
714 assert_eq!(config.query_timeout, Some(Duration::from_secs(30)));
715 assert_eq!(config.gc_interval, 100);
716 }
717
718 #[test]
719 fn test_config_in_memory() {
720 let config = Config::in_memory();
721 assert!(config.path.is_none());
722 assert!(!config.wal_enabled);
723 assert!(config.backward_edges);
724 }
725
726 #[test]
727 fn test_config_persistent() {
728 let config = Config::persistent("/tmp/test_db");
729 assert_eq!(
730 config.path.as_deref(),
731 Some(std::path::Path::new("/tmp/test_db"))
732 );
733 assert!(config.wal_enabled);
734 }
735
736 #[test]
737 fn test_config_with_memory_limit() {
738 let config = Config::in_memory().with_memory_limit(1024 * 1024);
739 assert_eq!(config.memory_limit, Some(1024 * 1024));
740 }
741
742 #[test]
743 fn test_config_with_threads() {
744 let config = Config::in_memory().with_threads(8);
745 assert_eq!(config.threads, 8);
746 }
747
748 #[test]
749 fn test_config_without_backward_edges() {
750 let config = Config::in_memory().without_backward_edges();
751 assert!(!config.backward_edges);
752 }
753
754 #[test]
755 fn test_config_with_query_logging() {
756 let config = Config::in_memory().with_query_logging();
757 assert!(config.query_logging);
758 }
759
760 #[test]
761 fn test_config_with_spill_path() {
762 let config = Config::in_memory().with_spill_path("/tmp/spill");
763 assert_eq!(
764 config.spill_path.as_deref(),
765 Some(std::path::Path::new("/tmp/spill"))
766 );
767 }
768
769 #[test]
770 fn test_config_with_memory_fraction() {
771 let config = Config::in_memory().with_memory_fraction(0.5);
772 assert!(config.memory_limit.is_some());
773 assert!(config.memory_limit.unwrap() > 0);
774 }
775
776 #[test]
777 fn test_config_with_adaptive() {
778 let adaptive = AdaptiveConfig::default().with_threshold(5.0);
779 let config = Config::in_memory().with_adaptive(adaptive);
780 assert!((config.adaptive.threshold - 5.0).abs() < f64::EPSILON);
781 }
782
783 #[test]
784 fn test_config_without_adaptive() {
785 let config = Config::in_memory().without_adaptive();
786 assert!(!config.adaptive.enabled);
787 }
788
789 #[test]
790 fn test_config_without_factorized_execution() {
791 let config = Config::in_memory().without_factorized_execution();
792 assert!(!config.factorized_execution);
793 }
794
795 #[test]
796 fn test_config_builder_chaining() {
797 let config = Config::persistent("/tmp/db")
798 .with_memory_limit(512 * 1024 * 1024)
799 .with_threads(4)
800 .with_query_logging()
801 .without_backward_edges()
802 .with_spill_path("/tmp/spill");
803
804 assert!(config.path.is_some());
805 assert_eq!(config.memory_limit, Some(512 * 1024 * 1024));
806 assert_eq!(config.threads, 4);
807 assert!(config.query_logging);
808 assert!(!config.backward_edges);
809 assert!(config.spill_path.is_some());
810 }
811
812 #[test]
813 fn test_adaptive_config_default() {
814 let config = AdaptiveConfig::default();
815 assert!(config.enabled);
816 assert!((config.threshold - 3.0).abs() < f64::EPSILON);
817 assert_eq!(config.min_rows, 1000);
818 assert_eq!(config.max_reoptimizations, 3);
819 }
820
821 #[test]
822 fn test_adaptive_config_disabled() {
823 let config = AdaptiveConfig::disabled();
824 assert!(!config.enabled);
825 }
826
827 #[test]
828 fn test_adaptive_config_with_threshold() {
829 let config = AdaptiveConfig::default().with_threshold(10.0);
830 assert!((config.threshold - 10.0).abs() < f64::EPSILON);
831 }
832
833 #[test]
834 fn test_adaptive_config_with_min_rows() {
835 let config = AdaptiveConfig::default().with_min_rows(500);
836 assert_eq!(config.min_rows, 500);
837 }
838
839 #[test]
840 fn test_adaptive_config_with_max_reoptimizations() {
841 let config = AdaptiveConfig::default().with_max_reoptimizations(5);
842 assert_eq!(config.max_reoptimizations, 5);
843 }
844
845 #[test]
846 fn test_adaptive_config_builder_chaining() {
847 let config = AdaptiveConfig::default()
848 .with_threshold(2.0)
849 .with_min_rows(100)
850 .with_max_reoptimizations(10);
851 assert!((config.threshold - 2.0).abs() < f64::EPSILON);
852 assert_eq!(config.min_rows, 100);
853 assert_eq!(config.max_reoptimizations, 10);
854 }
855
856 #[test]
859 fn test_graph_model_default_is_lpg() {
860 assert_eq!(GraphModel::default(), GraphModel::Lpg);
861 }
862
863 #[test]
864 fn test_graph_model_display() {
865 assert_eq!(GraphModel::Lpg.to_string(), "LPG");
866 assert_eq!(GraphModel::Rdf.to_string(), "RDF");
867 }
868
869 #[test]
870 fn test_config_with_graph_model() {
871 let config = Config::in_memory().with_graph_model(GraphModel::Rdf);
872 assert_eq!(config.graph_model, GraphModel::Rdf);
873 }
874
875 #[test]
878 fn test_durability_mode_default_is_batch() {
879 let mode = DurabilityMode::default();
880 assert_eq!(
881 mode,
882 DurabilityMode::Batch {
883 max_delay_ms: 100,
884 max_records: 1000
885 }
886 );
887 }
888
889 #[test]
890 fn test_config_with_wal_durability() {
891 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::Sync);
892 assert_eq!(config.wal_durability, DurabilityMode::Sync);
893 }
894
895 #[test]
896 fn test_config_with_wal_durability_nosync() {
897 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::NoSync);
898 assert_eq!(config.wal_durability, DurabilityMode::NoSync);
899 }
900
901 #[test]
902 fn test_config_with_wal_durability_adaptive() {
903 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::Adaptive {
904 target_interval_ms: 50,
905 });
906 assert_eq!(
907 config.wal_durability,
908 DurabilityMode::Adaptive {
909 target_interval_ms: 50
910 }
911 );
912 }
913
914 #[test]
917 fn test_config_default_max_property_size() {
918 let config = Config::in_memory();
919 assert_eq!(config.max_property_size, Some(16 * 1024 * 1024));
920 }
921
922 #[test]
923 fn test_config_with_max_property_size() {
924 let config = Config::in_memory().with_max_property_size(1024);
925 assert_eq!(config.max_property_size, Some(1024));
926 }
927
928 #[test]
929 fn test_config_without_max_property_size() {
930 let config = Config::in_memory().without_max_property_size();
931 assert!(config.max_property_size.is_none());
932 }
933
934 #[test]
937 fn test_config_with_schema_constraints() {
938 let config = Config::in_memory().with_schema_constraints();
939 assert!(config.schema_constraints);
940 }
941
942 #[test]
945 fn test_config_with_query_timeout() {
946 let config = Config::in_memory().with_query_timeout(Duration::from_mins(1));
947 assert_eq!(config.query_timeout, Some(Duration::from_mins(1)));
948 }
949
950 #[test]
951 fn test_config_without_query_timeout() {
952 let config = Config::in_memory().without_query_timeout();
953 assert!(config.query_timeout.is_none());
954 }
955
956 #[test]
957 fn test_config_default_query_timeout() {
958 let config = Config::in_memory();
959 assert_eq!(config.query_timeout, Some(Duration::from_secs(30)));
960 }
961
962 #[test]
965 fn test_config_with_gc_interval() {
966 let config = Config::in_memory().with_gc_interval(50);
967 assert_eq!(config.gc_interval, 50);
968 }
969
970 #[test]
971 fn test_config_gc_disabled() {
972 let config = Config::in_memory().with_gc_interval(0);
973 assert_eq!(config.gc_interval, 0);
974 }
975
976 #[test]
979 fn test_validate_default_config() {
980 assert!(Config::default().validate().is_ok());
981 }
982
983 #[test]
984 fn test_validate_in_memory_config() {
985 assert!(Config::in_memory().validate().is_ok());
986 }
987
988 #[test]
989 fn test_validate_rejects_zero_memory_limit() {
990 let config = Config::in_memory().with_memory_limit(0);
991 assert_eq!(config.validate(), Err(ConfigError::ZeroMemoryLimit));
992 }
993
994 #[test]
995 fn test_validate_rejects_zero_threads() {
996 let config = Config::in_memory().with_threads(0);
997 assert_eq!(config.validate(), Err(ConfigError::ZeroThreads));
998 }
999
1000 #[test]
1001 fn test_validate_rejects_zero_wal_flush_interval() {
1002 let mut config = Config::in_memory();
1003 config.wal_flush_interval_ms = 0;
1004 assert_eq!(config.validate(), Err(ConfigError::ZeroWalFlushInterval));
1005 }
1006
1007 #[cfg(not(feature = "triple-store"))]
1008 #[test]
1009 fn test_validate_rejects_rdf_without_feature() {
1010 let config = Config::in_memory().with_graph_model(GraphModel::Rdf);
1011 assert_eq!(config.validate(), Err(ConfigError::RdfFeatureRequired));
1012 }
1013
1014 #[test]
1015 fn test_config_error_display() {
1016 assert_eq!(
1017 ConfigError::ZeroMemoryLimit.to_string(),
1018 "memory_limit must be greater than zero"
1019 );
1020 assert_eq!(
1021 ConfigError::ZeroThreads.to_string(),
1022 "threads must be greater than zero"
1023 );
1024 assert_eq!(
1025 ConfigError::ZeroWalFlushInterval.to_string(),
1026 "wal_flush_interval_ms must be greater than zero"
1027 );
1028 assert_eq!(
1029 ConfigError::RdfFeatureRequired.to_string(),
1030 "RDF graph model requires the `rdf` feature flag to be enabled"
1031 );
1032 }
1033
1034 #[test]
1037 fn test_config_full_builder_chaining() {
1038 let config = Config::persistent("/tmp/db")
1039 .with_graph_model(GraphModel::Lpg)
1040 .with_memory_limit(512 * 1024 * 1024)
1041 .with_threads(4)
1042 .with_query_logging()
1043 .with_wal_durability(DurabilityMode::Sync)
1044 .with_schema_constraints()
1045 .without_backward_edges()
1046 .with_spill_path("/tmp/spill")
1047 .with_query_timeout(Duration::from_mins(1));
1048
1049 assert_eq!(config.graph_model, GraphModel::Lpg);
1050 assert!(config.path.is_some());
1051 assert_eq!(config.memory_limit, Some(512 * 1024 * 1024));
1052 assert_eq!(config.threads, 4);
1053 assert!(config.query_logging);
1054 assert_eq!(config.wal_durability, DurabilityMode::Sync);
1055 assert!(config.schema_constraints);
1056 assert!(!config.backward_edges);
1057 assert!(config.spill_path.is_some());
1058 assert_eq!(config.query_timeout, Some(Duration::from_mins(1)));
1059 assert!(config.validate().is_ok());
1060 }
1061
1062 #[test]
1065 fn test_access_mode_default_is_read_write() {
1066 assert_eq!(AccessMode::default(), AccessMode::ReadWrite);
1067 }
1068
1069 #[test]
1070 fn test_access_mode_display() {
1071 assert_eq!(AccessMode::ReadWrite.to_string(), "read-write");
1072 assert_eq!(AccessMode::ReadOnly.to_string(), "read-only");
1073 }
1074
1075 #[test]
1076 fn test_config_with_access_mode() {
1077 let config = Config::persistent("/tmp/db").with_access_mode(AccessMode::ReadOnly);
1078 assert_eq!(config.access_mode, AccessMode::ReadOnly);
1079 }
1080
1081 #[test]
1082 fn test_config_read_only() {
1083 let config = Config::read_only("/tmp/db.grafeo");
1084 assert_eq!(config.access_mode, AccessMode::ReadOnly);
1085 assert!(config.path.is_some());
1086 assert!(!config.wal_enabled);
1087 }
1088
1089 #[test]
1090 fn test_config_default_is_read_write() {
1091 let config = Config::default();
1092 assert_eq!(config.access_mode, AccessMode::ReadWrite);
1093 }
1094
1095 #[test]
1098 fn test_storage_format_default_is_auto() {
1099 assert_eq!(StorageFormat::default(), StorageFormat::Auto);
1100 }
1101
1102 #[test]
1103 fn test_storage_format_display() {
1104 assert_eq!(StorageFormat::Auto.to_string(), "auto");
1105 assert_eq!(StorageFormat::WalDirectory.to_string(), "wal-directory");
1106 assert_eq!(StorageFormat::SingleFile.to_string(), "single-file");
1107 }
1108
1109 #[test]
1110 fn test_config_with_storage_format() {
1111 let config = Config::in_memory().with_storage_format(StorageFormat::SingleFile);
1112 assert_eq!(config.storage_format, StorageFormat::SingleFile);
1113
1114 let config2 = Config::in_memory().with_storage_format(StorageFormat::WalDirectory);
1115 assert_eq!(config2.storage_format, StorageFormat::WalDirectory);
1116 }
1117
1118 #[test]
1121 fn test_config_with_cdc() {
1122 let config = Config::in_memory().with_cdc();
1123 assert!(config.cdc_enabled);
1124 }
1125
1126 #[test]
1127 fn test_config_cdc_default_false() {
1128 let config = Config::default();
1129 assert!(!config.cdc_enabled);
1130 }
1131
1132 #[test]
1135 fn test_config_error_is_std_error() {
1136 let err = ConfigError::ZeroMemoryLimit;
1137 let dyn_err: &dyn std::error::Error = &err;
1139 assert!(dyn_err.source().is_none());
1140 assert!(!dyn_err.to_string().is_empty());
1141 }
1142
1143 #[test]
1146 fn test_validate_accepts_nonzero_memory_limit() {
1147 let config = Config::in_memory().with_memory_limit(1);
1148 assert!(config.validate().is_ok());
1149 }
1150
1151 #[test]
1152 fn test_validate_accepts_none_memory_limit() {
1153 let config = Config::in_memory();
1154 assert!(config.memory_limit.is_none());
1155 assert!(config.validate().is_ok());
1156 }
1157
1158 #[test]
1161 fn test_durability_mode_debug() {
1162 let sync = DurabilityMode::Sync;
1163 let debug = format!("{sync:?}");
1164 assert_eq!(debug, "Sync");
1165
1166 let no_sync = DurabilityMode::NoSync;
1167 let debug = format!("{no_sync:?}");
1168 assert_eq!(debug, "NoSync");
1169 }
1170
1171 #[test]
1174 fn test_read_only_config_full() {
1175 let config = Config::read_only("/tmp/data.grafeo");
1176 assert_eq!(config.access_mode, AccessMode::ReadOnly);
1177 assert!(!config.wal_enabled);
1178 assert!(config.path.is_some());
1179 assert!(config.backward_edges);
1181 assert_eq!(config.graph_model, GraphModel::Lpg);
1182 }
1183}