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]
664 pub fn with_section_tier(
665 self,
666 section_type: grafeo_common::storage::SectionType,
667 tier: grafeo_common::storage::TierOverride,
668 ) -> Self {
669 let existing_max_ram = self
670 .section_configs
671 .get(§ion_type)
672 .and_then(|c| c.max_ram);
673 self.with_section_config(
674 section_type,
675 grafeo_common::storage::SectionMemoryConfig {
676 max_ram: existing_max_ram,
677 tier,
678 },
679 )
680 }
681
682 #[must_use]
687 pub fn with_checkpoint_interval(mut self, interval: Duration) -> Self {
688 self.checkpoint_interval = Some(interval);
689 self
690 }
691
692 pub fn validate(&self) -> std::result::Result<(), ConfigError> {
700 if let Some(limit) = self.memory_limit
701 && limit == 0
702 {
703 return Err(ConfigError::ZeroMemoryLimit);
704 }
705
706 if self.threads == 0 {
707 return Err(ConfigError::ZeroThreads);
708 }
709
710 if self.wal_flush_interval_ms == 0 {
711 return Err(ConfigError::ZeroWalFlushInterval);
712 }
713
714 #[cfg(not(feature = "triple-store"))]
715 if self.graph_model == GraphModel::Rdf {
716 return Err(ConfigError::RdfFeatureRequired);
717 }
718
719 Ok(())
720 }
721}
722
723mod num_cpus {
725 #[cfg(not(target_arch = "wasm32"))]
726 pub fn get() -> usize {
727 std::thread::available_parallelism().map_or(4, |n| n.get())
728 }
729
730 #[cfg(target_arch = "wasm32")]
731 pub fn get() -> usize {
732 1
733 }
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739
740 #[test]
741 fn test_config_default() {
742 let config = Config::default();
743 assert_eq!(config.graph_model, GraphModel::Lpg);
744 assert!(config.path.is_none());
745 assert!(config.memory_limit.is_none());
746 assert!(config.spill_path.is_none());
747 assert!(config.threads > 0);
748 assert!(config.wal_enabled);
749 assert_eq!(config.wal_flush_interval_ms, 100);
750 assert!(config.backward_edges);
751 assert!(!config.query_logging);
752 assert!(config.factorized_execution);
753 assert_eq!(config.wal_durability, DurabilityMode::default());
754 assert!(!config.schema_constraints);
755 assert_eq!(config.query_timeout, Some(Duration::from_secs(30)));
756 assert_eq!(config.gc_interval, 100);
757 }
758
759 #[test]
760 fn test_config_in_memory() {
761 let config = Config::in_memory();
762 assert!(config.path.is_none());
763 assert!(!config.wal_enabled);
764 assert!(config.backward_edges);
765 }
766
767 #[test]
768 fn test_config_persistent() {
769 let config = Config::persistent("/tmp/test_db");
770 assert_eq!(
771 config.path.as_deref(),
772 Some(std::path::Path::new("/tmp/test_db"))
773 );
774 assert!(config.wal_enabled);
775 }
776
777 #[test]
778 fn test_config_with_memory_limit() {
779 let config = Config::in_memory().with_memory_limit(1024 * 1024);
780 assert_eq!(config.memory_limit, Some(1024 * 1024));
781 }
782
783 #[test]
784 fn test_config_with_threads() {
785 let config = Config::in_memory().with_threads(8);
786 assert_eq!(config.threads, 8);
787 }
788
789 #[test]
790 fn test_config_without_backward_edges() {
791 let config = Config::in_memory().without_backward_edges();
792 assert!(!config.backward_edges);
793 }
794
795 #[test]
796 fn test_config_with_query_logging() {
797 let config = Config::in_memory().with_query_logging();
798 assert!(config.query_logging);
799 }
800
801 #[test]
802 fn test_config_with_spill_path() {
803 let config = Config::in_memory().with_spill_path("/tmp/spill");
804 assert_eq!(
805 config.spill_path.as_deref(),
806 Some(std::path::Path::new("/tmp/spill"))
807 );
808 }
809
810 #[test]
811 fn test_config_with_memory_fraction() {
812 let config = Config::in_memory().with_memory_fraction(0.5);
813 assert!(config.memory_limit.is_some());
814 assert!(config.memory_limit.unwrap() > 0);
815 }
816
817 #[test]
818 fn test_config_with_adaptive() {
819 let adaptive = AdaptiveConfig::default().with_threshold(5.0);
820 let config = Config::in_memory().with_adaptive(adaptive);
821 assert!((config.adaptive.threshold - 5.0).abs() < f64::EPSILON);
822 }
823
824 #[test]
825 fn test_config_without_adaptive() {
826 let config = Config::in_memory().without_adaptive();
827 assert!(!config.adaptive.enabled);
828 }
829
830 #[test]
831 fn test_config_without_factorized_execution() {
832 let config = Config::in_memory().without_factorized_execution();
833 assert!(!config.factorized_execution);
834 }
835
836 #[test]
837 fn test_config_builder_chaining() {
838 let config = Config::persistent("/tmp/db")
839 .with_memory_limit(512 * 1024 * 1024)
840 .with_threads(4)
841 .with_query_logging()
842 .without_backward_edges()
843 .with_spill_path("/tmp/spill");
844
845 assert!(config.path.is_some());
846 assert_eq!(config.memory_limit, Some(512 * 1024 * 1024));
847 assert_eq!(config.threads, 4);
848 assert!(config.query_logging);
849 assert!(!config.backward_edges);
850 assert!(config.spill_path.is_some());
851 }
852
853 #[test]
854 fn test_adaptive_config_default() {
855 let config = AdaptiveConfig::default();
856 assert!(config.enabled);
857 assert!((config.threshold - 3.0).abs() < f64::EPSILON);
858 assert_eq!(config.min_rows, 1000);
859 assert_eq!(config.max_reoptimizations, 3);
860 }
861
862 #[test]
863 fn test_adaptive_config_disabled() {
864 let config = AdaptiveConfig::disabled();
865 assert!(!config.enabled);
866 }
867
868 #[test]
869 fn test_adaptive_config_with_threshold() {
870 let config = AdaptiveConfig::default().with_threshold(10.0);
871 assert!((config.threshold - 10.0).abs() < f64::EPSILON);
872 }
873
874 #[test]
875 fn test_adaptive_config_with_min_rows() {
876 let config = AdaptiveConfig::default().with_min_rows(500);
877 assert_eq!(config.min_rows, 500);
878 }
879
880 #[test]
881 fn test_adaptive_config_with_max_reoptimizations() {
882 let config = AdaptiveConfig::default().with_max_reoptimizations(5);
883 assert_eq!(config.max_reoptimizations, 5);
884 }
885
886 #[test]
887 fn test_adaptive_config_builder_chaining() {
888 let config = AdaptiveConfig::default()
889 .with_threshold(2.0)
890 .with_min_rows(100)
891 .with_max_reoptimizations(10);
892 assert!((config.threshold - 2.0).abs() < f64::EPSILON);
893 assert_eq!(config.min_rows, 100);
894 assert_eq!(config.max_reoptimizations, 10);
895 }
896
897 #[test]
900 fn test_graph_model_default_is_lpg() {
901 assert_eq!(GraphModel::default(), GraphModel::Lpg);
902 }
903
904 #[test]
905 fn test_graph_model_display() {
906 assert_eq!(GraphModel::Lpg.to_string(), "LPG");
907 assert_eq!(GraphModel::Rdf.to_string(), "RDF");
908 }
909
910 #[test]
911 fn test_config_with_graph_model() {
912 let config = Config::in_memory().with_graph_model(GraphModel::Rdf);
913 assert_eq!(config.graph_model, GraphModel::Rdf);
914 }
915
916 #[test]
919 fn test_durability_mode_default_is_batch() {
920 let mode = DurabilityMode::default();
921 assert_eq!(
922 mode,
923 DurabilityMode::Batch {
924 max_delay_ms: 100,
925 max_records: 1000
926 }
927 );
928 }
929
930 #[test]
931 fn test_config_with_wal_durability() {
932 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::Sync);
933 assert_eq!(config.wal_durability, DurabilityMode::Sync);
934 }
935
936 #[test]
937 fn test_config_with_wal_durability_nosync() {
938 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::NoSync);
939 assert_eq!(config.wal_durability, DurabilityMode::NoSync);
940 }
941
942 #[test]
943 fn test_config_with_wal_durability_adaptive() {
944 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::Adaptive {
945 target_interval_ms: 50,
946 });
947 assert_eq!(
948 config.wal_durability,
949 DurabilityMode::Adaptive {
950 target_interval_ms: 50
951 }
952 );
953 }
954
955 #[test]
958 fn test_config_default_max_property_size() {
959 let config = Config::in_memory();
960 assert_eq!(config.max_property_size, Some(16 * 1024 * 1024));
961 }
962
963 #[test]
964 fn test_config_with_max_property_size() {
965 let config = Config::in_memory().with_max_property_size(1024);
966 assert_eq!(config.max_property_size, Some(1024));
967 }
968
969 #[test]
970 fn test_config_without_max_property_size() {
971 let config = Config::in_memory().without_max_property_size();
972 assert!(config.max_property_size.is_none());
973 }
974
975 #[test]
978 fn test_config_with_schema_constraints() {
979 let config = Config::in_memory().with_schema_constraints();
980 assert!(config.schema_constraints);
981 }
982
983 #[test]
986 fn test_config_with_query_timeout() {
987 let config = Config::in_memory().with_query_timeout(Duration::from_mins(1));
988 assert_eq!(config.query_timeout, Some(Duration::from_mins(1)));
989 }
990
991 #[test]
992 fn test_config_without_query_timeout() {
993 let config = Config::in_memory().without_query_timeout();
994 assert!(config.query_timeout.is_none());
995 }
996
997 #[test]
998 fn test_config_default_query_timeout() {
999 let config = Config::in_memory();
1000 assert_eq!(config.query_timeout, Some(Duration::from_secs(30)));
1001 }
1002
1003 #[test]
1006 fn test_config_with_gc_interval() {
1007 let config = Config::in_memory().with_gc_interval(50);
1008 assert_eq!(config.gc_interval, 50);
1009 }
1010
1011 #[test]
1012 fn test_config_gc_disabled() {
1013 let config = Config::in_memory().with_gc_interval(0);
1014 assert_eq!(config.gc_interval, 0);
1015 }
1016
1017 #[test]
1020 fn test_validate_default_config() {
1021 assert!(Config::default().validate().is_ok());
1022 }
1023
1024 #[test]
1025 fn test_validate_in_memory_config() {
1026 assert!(Config::in_memory().validate().is_ok());
1027 }
1028
1029 #[test]
1030 fn test_validate_rejects_zero_memory_limit() {
1031 let config = Config::in_memory().with_memory_limit(0);
1032 assert_eq!(config.validate(), Err(ConfigError::ZeroMemoryLimit));
1033 }
1034
1035 #[test]
1036 fn test_validate_rejects_zero_threads() {
1037 let config = Config::in_memory().with_threads(0);
1038 assert_eq!(config.validate(), Err(ConfigError::ZeroThreads));
1039 }
1040
1041 #[test]
1042 fn test_validate_rejects_zero_wal_flush_interval() {
1043 let mut config = Config::in_memory();
1044 config.wal_flush_interval_ms = 0;
1045 assert_eq!(config.validate(), Err(ConfigError::ZeroWalFlushInterval));
1046 }
1047
1048 #[cfg(not(feature = "triple-store"))]
1049 #[test]
1050 fn test_validate_rejects_rdf_without_feature() {
1051 let config = Config::in_memory().with_graph_model(GraphModel::Rdf);
1052 assert_eq!(config.validate(), Err(ConfigError::RdfFeatureRequired));
1053 }
1054
1055 #[test]
1056 fn test_config_error_display() {
1057 assert_eq!(
1058 ConfigError::ZeroMemoryLimit.to_string(),
1059 "memory_limit must be greater than zero"
1060 );
1061 assert_eq!(
1062 ConfigError::ZeroThreads.to_string(),
1063 "threads must be greater than zero"
1064 );
1065 assert_eq!(
1066 ConfigError::ZeroWalFlushInterval.to_string(),
1067 "wal_flush_interval_ms must be greater than zero"
1068 );
1069 assert_eq!(
1070 ConfigError::RdfFeatureRequired.to_string(),
1071 "RDF graph model requires the `rdf` feature flag to be enabled"
1072 );
1073 }
1074
1075 #[test]
1078 fn test_config_full_builder_chaining() {
1079 let config = Config::persistent("/tmp/db")
1080 .with_graph_model(GraphModel::Lpg)
1081 .with_memory_limit(512 * 1024 * 1024)
1082 .with_threads(4)
1083 .with_query_logging()
1084 .with_wal_durability(DurabilityMode::Sync)
1085 .with_schema_constraints()
1086 .without_backward_edges()
1087 .with_spill_path("/tmp/spill")
1088 .with_query_timeout(Duration::from_mins(1));
1089
1090 assert_eq!(config.graph_model, GraphModel::Lpg);
1091 assert!(config.path.is_some());
1092 assert_eq!(config.memory_limit, Some(512 * 1024 * 1024));
1093 assert_eq!(config.threads, 4);
1094 assert!(config.query_logging);
1095 assert_eq!(config.wal_durability, DurabilityMode::Sync);
1096 assert!(config.schema_constraints);
1097 assert!(!config.backward_edges);
1098 assert!(config.spill_path.is_some());
1099 assert_eq!(config.query_timeout, Some(Duration::from_mins(1)));
1100 assert!(config.validate().is_ok());
1101 }
1102
1103 #[test]
1106 fn test_access_mode_default_is_read_write() {
1107 assert_eq!(AccessMode::default(), AccessMode::ReadWrite);
1108 }
1109
1110 #[test]
1111 fn test_access_mode_display() {
1112 assert_eq!(AccessMode::ReadWrite.to_string(), "read-write");
1113 assert_eq!(AccessMode::ReadOnly.to_string(), "read-only");
1114 }
1115
1116 #[test]
1117 fn test_config_with_access_mode() {
1118 let config = Config::persistent("/tmp/db").with_access_mode(AccessMode::ReadOnly);
1119 assert_eq!(config.access_mode, AccessMode::ReadOnly);
1120 }
1121
1122 #[test]
1123 fn test_config_read_only() {
1124 let config = Config::read_only("/tmp/db.grafeo");
1125 assert_eq!(config.access_mode, AccessMode::ReadOnly);
1126 assert!(config.path.is_some());
1127 assert!(!config.wal_enabled);
1128 }
1129
1130 #[test]
1131 fn test_config_default_is_read_write() {
1132 let config = Config::default();
1133 assert_eq!(config.access_mode, AccessMode::ReadWrite);
1134 }
1135
1136 #[test]
1139 fn test_storage_format_default_is_auto() {
1140 assert_eq!(StorageFormat::default(), StorageFormat::Auto);
1141 }
1142
1143 #[test]
1144 fn test_storage_format_display() {
1145 assert_eq!(StorageFormat::Auto.to_string(), "auto");
1146 assert_eq!(StorageFormat::WalDirectory.to_string(), "wal-directory");
1147 assert_eq!(StorageFormat::SingleFile.to_string(), "single-file");
1148 }
1149
1150 #[test]
1151 fn test_config_with_storage_format() {
1152 let config = Config::in_memory().with_storage_format(StorageFormat::SingleFile);
1153 assert_eq!(config.storage_format, StorageFormat::SingleFile);
1154
1155 let config2 = Config::in_memory().with_storage_format(StorageFormat::WalDirectory);
1156 assert_eq!(config2.storage_format, StorageFormat::WalDirectory);
1157 }
1158
1159 #[test]
1162 fn test_config_with_cdc() {
1163 let config = Config::in_memory().with_cdc();
1164 assert!(config.cdc_enabled);
1165 }
1166
1167 #[test]
1168 fn test_config_cdc_default_false() {
1169 let config = Config::default();
1170 assert!(!config.cdc_enabled);
1171 }
1172
1173 #[test]
1176 fn test_config_error_is_std_error() {
1177 let err = ConfigError::ZeroMemoryLimit;
1178 let dyn_err: &dyn std::error::Error = &err;
1180 assert!(dyn_err.source().is_none());
1181 assert!(!dyn_err.to_string().is_empty());
1182 }
1183
1184 #[test]
1187 fn test_validate_accepts_nonzero_memory_limit() {
1188 let config = Config::in_memory().with_memory_limit(1);
1189 assert!(config.validate().is_ok());
1190 }
1191
1192 #[test]
1193 fn test_validate_accepts_none_memory_limit() {
1194 let config = Config::in_memory();
1195 assert!(config.memory_limit.is_none());
1196 assert!(config.validate().is_ok());
1197 }
1198
1199 #[test]
1202 fn test_durability_mode_debug() {
1203 let sync = DurabilityMode::Sync;
1204 let debug = format!("{sync:?}");
1205 assert_eq!(debug, "Sync");
1206
1207 let no_sync = DurabilityMode::NoSync;
1208 let debug = format!("{no_sync:?}");
1209 assert_eq!(debug, "NoSync");
1210 }
1211
1212 #[test]
1215 fn test_read_only_config_full() {
1216 let config = Config::read_only("/tmp/data.grafeo");
1217 assert_eq!(config.access_mode, AccessMode::ReadOnly);
1218 assert!(!config.wal_enabled);
1219 assert!(config.path.is_some());
1220 assert!(config.backward_edges);
1222 assert_eq!(config.graph_model, GraphModel::Lpg);
1223 }
1224}