1use std::fmt;
4use std::path::PathBuf;
5use std::time::Duration;
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16pub enum GraphModel {
17 #[default]
19 Lpg,
20 Rdf,
22}
23
24impl fmt::Display for GraphModel {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 Self::Lpg => write!(f, "LPG"),
28 Self::Rdf => write!(f, "RDF"),
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
40#[non_exhaustive]
41pub enum AccessMode {
42 #[default]
44 ReadWrite,
45 ReadOnly,
49}
50
51impl fmt::Display for AccessMode {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::ReadWrite => write!(f, "read-write"),
55 Self::ReadOnly => write!(f, "read-only"),
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
66#[non_exhaustive]
67pub enum StorageFormat {
68 #[default]
71 Auto,
72 WalDirectory,
74 SingleFile,
77}
78
79impl fmt::Display for StorageFormat {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Self::Auto => write!(f, "auto"),
83 Self::WalDirectory => write!(f, "wal-directory"),
84 Self::SingleFile => write!(f, "single-file"),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95#[non_exhaustive]
96pub enum DurabilityMode {
97 Sync,
99 Batch {
101 max_delay_ms: u64,
103 max_records: u64,
105 },
106 Adaptive {
108 target_interval_ms: u64,
110 },
111 NoSync,
113}
114
115impl Default for DurabilityMode {
116 fn default() -> Self {
117 Self::Batch {
118 max_delay_ms: 100,
119 max_records: 1000,
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
126#[non_exhaustive]
127pub enum ConfigError {
128 ZeroMemoryLimit,
130 ZeroThreads,
132 ZeroWalFlushInterval,
134 RdfFeatureRequired,
136}
137
138impl fmt::Display for ConfigError {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 Self::ZeroMemoryLimit => write!(f, "memory_limit must be greater than zero"),
142 Self::ZeroThreads => write!(f, "threads must be greater than zero"),
143 Self::ZeroWalFlushInterval => {
144 write!(f, "wal_flush_interval_ms must be greater than zero")
145 }
146 Self::RdfFeatureRequired => {
147 write!(
148 f,
149 "RDF graph model requires the `rdf` feature flag to be enabled"
150 )
151 }
152 }
153 }
154}
155
156impl std::error::Error for ConfigError {}
157
158#[derive(Debug, Clone)]
160#[allow(clippy::struct_excessive_bools)] pub struct Config {
162 pub graph_model: GraphModel,
164 pub path: Option<PathBuf>,
166
167 pub memory_limit: Option<usize>,
169
170 pub spill_path: Option<PathBuf>,
172
173 pub threads: usize,
175
176 pub wal_enabled: bool,
178
179 pub wal_flush_interval_ms: u64,
181
182 pub backward_edges: bool,
184
185 pub query_logging: bool,
187
188 pub adaptive: AdaptiveConfig,
190
191 pub factorized_execution: bool,
199
200 pub wal_durability: DurabilityMode,
202
203 pub storage_format: StorageFormat,
208
209 pub schema_constraints: bool,
215
216 pub query_timeout: Option<Duration>,
222
223 pub gc_interval: usize,
228
229 pub access_mode: AccessMode,
235
236 pub cdc_enabled: bool,
247
248 #[cfg(feature = "cdc")]
255 pub cdc_retention: crate::cdc::CdcRetentionConfig,
256
257 pub section_configs: hashbrown::HashMap<
263 grafeo_common::storage::SectionType,
264 grafeo_common::storage::SectionMemoryConfig,
265 >,
266
267 pub checkpoint_interval: Option<Duration>,
273}
274
275#[derive(Debug, Clone)]
280pub struct AdaptiveConfig {
281 pub enabled: bool,
283
284 pub threshold: f64,
289
290 pub min_rows: u64,
294
295 pub max_reoptimizations: usize,
297}
298
299impl Default for AdaptiveConfig {
300 fn default() -> Self {
301 Self {
302 enabled: true,
303 threshold: 3.0,
304 min_rows: 1000,
305 max_reoptimizations: 3,
306 }
307 }
308}
309
310impl AdaptiveConfig {
311 #[must_use]
313 pub fn disabled() -> Self {
314 Self {
315 enabled: false,
316 ..Default::default()
317 }
318 }
319
320 #[must_use]
322 pub fn with_threshold(mut self, threshold: f64) -> Self {
323 self.threshold = threshold;
324 self
325 }
326
327 #[must_use]
329 pub fn with_min_rows(mut self, min_rows: u64) -> Self {
330 self.min_rows = min_rows;
331 self
332 }
333
334 #[must_use]
336 pub fn with_max_reoptimizations(mut self, max: usize) -> Self {
337 self.max_reoptimizations = max;
338 self
339 }
340}
341
342impl Default for Config {
343 fn default() -> Self {
344 Self {
345 graph_model: GraphModel::default(),
346 path: None,
347 memory_limit: None,
348 spill_path: None,
349 threads: num_cpus::get(),
350 wal_enabled: true,
351 wal_flush_interval_ms: 100,
352 backward_edges: true,
353 query_logging: false,
354 adaptive: AdaptiveConfig::default(),
355 factorized_execution: true,
356 wal_durability: DurabilityMode::default(),
357 storage_format: StorageFormat::default(),
358 schema_constraints: false,
359 query_timeout: None,
360 gc_interval: 100,
361 access_mode: AccessMode::default(),
362 cdc_enabled: false,
363 #[cfg(feature = "cdc")]
364 cdc_retention: crate::cdc::CdcRetentionConfig::default(),
365 section_configs: hashbrown::HashMap::new(),
366 checkpoint_interval: None,
367 }
368 }
369}
370
371impl Config {
372 #[must_use]
374 pub fn in_memory() -> Self {
375 Self {
376 path: None,
377 wal_enabled: false,
378 ..Default::default()
379 }
380 }
381
382 #[must_use]
384 pub fn persistent(path: impl Into<PathBuf>) -> Self {
385 Self {
386 path: Some(path.into()),
387 wal_enabled: true,
388 ..Default::default()
389 }
390 }
391
392 #[must_use]
394 pub fn with_memory_limit(mut self, limit: usize) -> Self {
395 self.memory_limit = Some(limit);
396 self
397 }
398
399 #[must_use]
401 pub fn with_threads(mut self, threads: usize) -> Self {
402 self.threads = threads;
403 self
404 }
405
406 #[must_use]
408 pub fn without_backward_edges(mut self) -> Self {
409 self.backward_edges = false;
410 self
411 }
412
413 #[must_use]
415 pub fn with_query_logging(mut self) -> Self {
416 self.query_logging = true;
417 self
418 }
419
420 #[must_use]
422 pub fn with_memory_fraction(mut self, fraction: f64) -> Self {
423 use grafeo_common::memory::buffer::BufferManagerConfig;
424 let system_memory = BufferManagerConfig::detect_system_memory();
425 self.memory_limit = Some((system_memory as f64 * fraction) as usize);
426 self
427 }
428
429 #[must_use]
431 pub fn with_spill_path(mut self, path: impl Into<PathBuf>) -> Self {
432 self.spill_path = Some(path.into());
433 self
434 }
435
436 #[must_use]
438 pub fn with_adaptive(mut self, adaptive: AdaptiveConfig) -> Self {
439 self.adaptive = adaptive;
440 self
441 }
442
443 #[must_use]
445 pub fn without_adaptive(mut self) -> Self {
446 self.adaptive.enabled = false;
447 self
448 }
449
450 #[must_use]
456 pub fn without_factorized_execution(mut self) -> Self {
457 self.factorized_execution = false;
458 self
459 }
460
461 #[must_use]
463 pub fn with_graph_model(mut self, model: GraphModel) -> Self {
464 self.graph_model = model;
465 self
466 }
467
468 #[must_use]
470 pub fn with_wal_durability(mut self, mode: DurabilityMode) -> Self {
471 self.wal_durability = mode;
472 self
473 }
474
475 #[must_use]
477 pub fn with_storage_format(mut self, format: StorageFormat) -> Self {
478 self.storage_format = format;
479 self
480 }
481
482 #[must_use]
484 pub fn with_schema_constraints(mut self) -> Self {
485 self.schema_constraints = true;
486 self
487 }
488
489 #[must_use]
491 pub fn with_query_timeout(mut self, timeout: Duration) -> Self {
492 self.query_timeout = Some(timeout);
493 self
494 }
495
496 #[must_use]
500 pub fn with_gc_interval(mut self, interval: usize) -> Self {
501 self.gc_interval = interval;
502 self
503 }
504
505 #[must_use]
507 pub fn with_access_mode(mut self, mode: AccessMode) -> Self {
508 self.access_mode = mode;
509 self
510 }
511
512 #[must_use]
517 pub fn read_only(path: impl Into<PathBuf>) -> Self {
518 Self {
519 path: Some(path.into()),
520 wal_enabled: false,
521 access_mode: AccessMode::ReadOnly,
522 ..Default::default()
523 }
524 }
525
526 #[must_use]
534 pub fn with_cdc(mut self) -> Self {
535 self.cdc_enabled = true;
536 self
537 }
538
539 #[must_use]
557 pub fn with_section_config(
558 mut self,
559 section_type: grafeo_common::storage::SectionType,
560 config: grafeo_common::storage::SectionMemoryConfig,
561 ) -> Self {
562 self.section_configs.insert(section_type, config);
563 self
564 }
565
566 #[must_use]
571 pub fn with_checkpoint_interval(mut self, interval: Duration) -> Self {
572 self.checkpoint_interval = Some(interval);
573 self
574 }
575
576 pub fn validate(&self) -> std::result::Result<(), ConfigError> {
584 if let Some(limit) = self.memory_limit
585 && limit == 0
586 {
587 return Err(ConfigError::ZeroMemoryLimit);
588 }
589
590 if self.threads == 0 {
591 return Err(ConfigError::ZeroThreads);
592 }
593
594 if self.wal_flush_interval_ms == 0 {
595 return Err(ConfigError::ZeroWalFlushInterval);
596 }
597
598 #[cfg(not(feature = "triple-store"))]
599 if self.graph_model == GraphModel::Rdf {
600 return Err(ConfigError::RdfFeatureRequired);
601 }
602
603 Ok(())
604 }
605}
606
607mod num_cpus {
609 #[cfg(not(target_arch = "wasm32"))]
610 pub fn get() -> usize {
611 std::thread::available_parallelism()
612 .map(|n| n.get())
613 .unwrap_or(4)
614 }
615
616 #[cfg(target_arch = "wasm32")]
617 pub fn get() -> usize {
618 1
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625
626 #[test]
627 fn test_config_default() {
628 let config = Config::default();
629 assert_eq!(config.graph_model, GraphModel::Lpg);
630 assert!(config.path.is_none());
631 assert!(config.memory_limit.is_none());
632 assert!(config.spill_path.is_none());
633 assert!(config.threads > 0);
634 assert!(config.wal_enabled);
635 assert_eq!(config.wal_flush_interval_ms, 100);
636 assert!(config.backward_edges);
637 assert!(!config.query_logging);
638 assert!(config.factorized_execution);
639 assert_eq!(config.wal_durability, DurabilityMode::default());
640 assert!(!config.schema_constraints);
641 assert!(config.query_timeout.is_none());
642 assert_eq!(config.gc_interval, 100);
643 }
644
645 #[test]
646 fn test_config_in_memory() {
647 let config = Config::in_memory();
648 assert!(config.path.is_none());
649 assert!(!config.wal_enabled);
650 assert!(config.backward_edges);
651 }
652
653 #[test]
654 fn test_config_persistent() {
655 let config = Config::persistent("/tmp/test_db");
656 assert_eq!(
657 config.path.as_deref(),
658 Some(std::path::Path::new("/tmp/test_db"))
659 );
660 assert!(config.wal_enabled);
661 }
662
663 #[test]
664 fn test_config_with_memory_limit() {
665 let config = Config::in_memory().with_memory_limit(1024 * 1024);
666 assert_eq!(config.memory_limit, Some(1024 * 1024));
667 }
668
669 #[test]
670 fn test_config_with_threads() {
671 let config = Config::in_memory().with_threads(8);
672 assert_eq!(config.threads, 8);
673 }
674
675 #[test]
676 fn test_config_without_backward_edges() {
677 let config = Config::in_memory().without_backward_edges();
678 assert!(!config.backward_edges);
679 }
680
681 #[test]
682 fn test_config_with_query_logging() {
683 let config = Config::in_memory().with_query_logging();
684 assert!(config.query_logging);
685 }
686
687 #[test]
688 fn test_config_with_spill_path() {
689 let config = Config::in_memory().with_spill_path("/tmp/spill");
690 assert_eq!(
691 config.spill_path.as_deref(),
692 Some(std::path::Path::new("/tmp/spill"))
693 );
694 }
695
696 #[test]
697 fn test_config_with_memory_fraction() {
698 let config = Config::in_memory().with_memory_fraction(0.5);
699 assert!(config.memory_limit.is_some());
700 assert!(config.memory_limit.unwrap() > 0);
701 }
702
703 #[test]
704 fn test_config_with_adaptive() {
705 let adaptive = AdaptiveConfig::default().with_threshold(5.0);
706 let config = Config::in_memory().with_adaptive(adaptive);
707 assert!((config.adaptive.threshold - 5.0).abs() < f64::EPSILON);
708 }
709
710 #[test]
711 fn test_config_without_adaptive() {
712 let config = Config::in_memory().without_adaptive();
713 assert!(!config.adaptive.enabled);
714 }
715
716 #[test]
717 fn test_config_without_factorized_execution() {
718 let config = Config::in_memory().without_factorized_execution();
719 assert!(!config.factorized_execution);
720 }
721
722 #[test]
723 fn test_config_builder_chaining() {
724 let config = Config::persistent("/tmp/db")
725 .with_memory_limit(512 * 1024 * 1024)
726 .with_threads(4)
727 .with_query_logging()
728 .without_backward_edges()
729 .with_spill_path("/tmp/spill");
730
731 assert!(config.path.is_some());
732 assert_eq!(config.memory_limit, Some(512 * 1024 * 1024));
733 assert_eq!(config.threads, 4);
734 assert!(config.query_logging);
735 assert!(!config.backward_edges);
736 assert!(config.spill_path.is_some());
737 }
738
739 #[test]
740 fn test_adaptive_config_default() {
741 let config = AdaptiveConfig::default();
742 assert!(config.enabled);
743 assert!((config.threshold - 3.0).abs() < f64::EPSILON);
744 assert_eq!(config.min_rows, 1000);
745 assert_eq!(config.max_reoptimizations, 3);
746 }
747
748 #[test]
749 fn test_adaptive_config_disabled() {
750 let config = AdaptiveConfig::disabled();
751 assert!(!config.enabled);
752 }
753
754 #[test]
755 fn test_adaptive_config_with_threshold() {
756 let config = AdaptiveConfig::default().with_threshold(10.0);
757 assert!((config.threshold - 10.0).abs() < f64::EPSILON);
758 }
759
760 #[test]
761 fn test_adaptive_config_with_min_rows() {
762 let config = AdaptiveConfig::default().with_min_rows(500);
763 assert_eq!(config.min_rows, 500);
764 }
765
766 #[test]
767 fn test_adaptive_config_with_max_reoptimizations() {
768 let config = AdaptiveConfig::default().with_max_reoptimizations(5);
769 assert_eq!(config.max_reoptimizations, 5);
770 }
771
772 #[test]
773 fn test_adaptive_config_builder_chaining() {
774 let config = AdaptiveConfig::default()
775 .with_threshold(2.0)
776 .with_min_rows(100)
777 .with_max_reoptimizations(10);
778 assert!((config.threshold - 2.0).abs() < f64::EPSILON);
779 assert_eq!(config.min_rows, 100);
780 assert_eq!(config.max_reoptimizations, 10);
781 }
782
783 #[test]
786 fn test_graph_model_default_is_lpg() {
787 assert_eq!(GraphModel::default(), GraphModel::Lpg);
788 }
789
790 #[test]
791 fn test_graph_model_display() {
792 assert_eq!(GraphModel::Lpg.to_string(), "LPG");
793 assert_eq!(GraphModel::Rdf.to_string(), "RDF");
794 }
795
796 #[test]
797 fn test_config_with_graph_model() {
798 let config = Config::in_memory().with_graph_model(GraphModel::Rdf);
799 assert_eq!(config.graph_model, GraphModel::Rdf);
800 }
801
802 #[test]
805 fn test_durability_mode_default_is_batch() {
806 let mode = DurabilityMode::default();
807 assert_eq!(
808 mode,
809 DurabilityMode::Batch {
810 max_delay_ms: 100,
811 max_records: 1000
812 }
813 );
814 }
815
816 #[test]
817 fn test_config_with_wal_durability() {
818 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::Sync);
819 assert_eq!(config.wal_durability, DurabilityMode::Sync);
820 }
821
822 #[test]
823 fn test_config_with_wal_durability_nosync() {
824 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::NoSync);
825 assert_eq!(config.wal_durability, DurabilityMode::NoSync);
826 }
827
828 #[test]
829 fn test_config_with_wal_durability_adaptive() {
830 let config = Config::persistent("/tmp/db").with_wal_durability(DurabilityMode::Adaptive {
831 target_interval_ms: 50,
832 });
833 assert_eq!(
834 config.wal_durability,
835 DurabilityMode::Adaptive {
836 target_interval_ms: 50
837 }
838 );
839 }
840
841 #[test]
844 fn test_config_with_schema_constraints() {
845 let config = Config::in_memory().with_schema_constraints();
846 assert!(config.schema_constraints);
847 }
848
849 #[test]
852 fn test_config_with_query_timeout() {
853 let config = Config::in_memory().with_query_timeout(Duration::from_secs(30));
854 assert_eq!(config.query_timeout, Some(Duration::from_secs(30)));
855 }
856
857 #[test]
860 fn test_config_with_gc_interval() {
861 let config = Config::in_memory().with_gc_interval(50);
862 assert_eq!(config.gc_interval, 50);
863 }
864
865 #[test]
866 fn test_config_gc_disabled() {
867 let config = Config::in_memory().with_gc_interval(0);
868 assert_eq!(config.gc_interval, 0);
869 }
870
871 #[test]
874 fn test_validate_default_config() {
875 assert!(Config::default().validate().is_ok());
876 }
877
878 #[test]
879 fn test_validate_in_memory_config() {
880 assert!(Config::in_memory().validate().is_ok());
881 }
882
883 #[test]
884 fn test_validate_rejects_zero_memory_limit() {
885 let config = Config::in_memory().with_memory_limit(0);
886 assert_eq!(config.validate(), Err(ConfigError::ZeroMemoryLimit));
887 }
888
889 #[test]
890 fn test_validate_rejects_zero_threads() {
891 let config = Config::in_memory().with_threads(0);
892 assert_eq!(config.validate(), Err(ConfigError::ZeroThreads));
893 }
894
895 #[test]
896 fn test_validate_rejects_zero_wal_flush_interval() {
897 let mut config = Config::in_memory();
898 config.wal_flush_interval_ms = 0;
899 assert_eq!(config.validate(), Err(ConfigError::ZeroWalFlushInterval));
900 }
901
902 #[cfg(not(feature = "triple-store"))]
903 #[test]
904 fn test_validate_rejects_rdf_without_feature() {
905 let config = Config::in_memory().with_graph_model(GraphModel::Rdf);
906 assert_eq!(config.validate(), Err(ConfigError::RdfFeatureRequired));
907 }
908
909 #[test]
910 fn test_config_error_display() {
911 assert_eq!(
912 ConfigError::ZeroMemoryLimit.to_string(),
913 "memory_limit must be greater than zero"
914 );
915 assert_eq!(
916 ConfigError::ZeroThreads.to_string(),
917 "threads must be greater than zero"
918 );
919 assert_eq!(
920 ConfigError::ZeroWalFlushInterval.to_string(),
921 "wal_flush_interval_ms must be greater than zero"
922 );
923 assert_eq!(
924 ConfigError::RdfFeatureRequired.to_string(),
925 "RDF graph model requires the `rdf` feature flag to be enabled"
926 );
927 }
928
929 #[test]
932 fn test_config_full_builder_chaining() {
933 let config = Config::persistent("/tmp/db")
934 .with_graph_model(GraphModel::Lpg)
935 .with_memory_limit(512 * 1024 * 1024)
936 .with_threads(4)
937 .with_query_logging()
938 .with_wal_durability(DurabilityMode::Sync)
939 .with_schema_constraints()
940 .without_backward_edges()
941 .with_spill_path("/tmp/spill")
942 .with_query_timeout(Duration::from_secs(60));
943
944 assert_eq!(config.graph_model, GraphModel::Lpg);
945 assert!(config.path.is_some());
946 assert_eq!(config.memory_limit, Some(512 * 1024 * 1024));
947 assert_eq!(config.threads, 4);
948 assert!(config.query_logging);
949 assert_eq!(config.wal_durability, DurabilityMode::Sync);
950 assert!(config.schema_constraints);
951 assert!(!config.backward_edges);
952 assert!(config.spill_path.is_some());
953 assert_eq!(config.query_timeout, Some(Duration::from_secs(60)));
954 assert!(config.validate().is_ok());
955 }
956
957 #[test]
960 fn test_access_mode_default_is_read_write() {
961 assert_eq!(AccessMode::default(), AccessMode::ReadWrite);
962 }
963
964 #[test]
965 fn test_access_mode_display() {
966 assert_eq!(AccessMode::ReadWrite.to_string(), "read-write");
967 assert_eq!(AccessMode::ReadOnly.to_string(), "read-only");
968 }
969
970 #[test]
971 fn test_config_with_access_mode() {
972 let config = Config::persistent("/tmp/db").with_access_mode(AccessMode::ReadOnly);
973 assert_eq!(config.access_mode, AccessMode::ReadOnly);
974 }
975
976 #[test]
977 fn test_config_read_only() {
978 let config = Config::read_only("/tmp/db.grafeo");
979 assert_eq!(config.access_mode, AccessMode::ReadOnly);
980 assert!(config.path.is_some());
981 assert!(!config.wal_enabled);
982 }
983
984 #[test]
985 fn test_config_default_is_read_write() {
986 let config = Config::default();
987 assert_eq!(config.access_mode, AccessMode::ReadWrite);
988 }
989
990 #[test]
993 fn test_storage_format_default_is_auto() {
994 assert_eq!(StorageFormat::default(), StorageFormat::Auto);
995 }
996
997 #[test]
998 fn test_storage_format_display() {
999 assert_eq!(StorageFormat::Auto.to_string(), "auto");
1000 assert_eq!(StorageFormat::WalDirectory.to_string(), "wal-directory");
1001 assert_eq!(StorageFormat::SingleFile.to_string(), "single-file");
1002 }
1003
1004 #[test]
1005 fn test_config_with_storage_format() {
1006 let config = Config::in_memory().with_storage_format(StorageFormat::SingleFile);
1007 assert_eq!(config.storage_format, StorageFormat::SingleFile);
1008
1009 let config2 = Config::in_memory().with_storage_format(StorageFormat::WalDirectory);
1010 assert_eq!(config2.storage_format, StorageFormat::WalDirectory);
1011 }
1012
1013 #[test]
1016 fn test_config_with_cdc() {
1017 let config = Config::in_memory().with_cdc();
1018 assert!(config.cdc_enabled);
1019 }
1020
1021 #[test]
1022 fn test_config_cdc_default_false() {
1023 let config = Config::default();
1024 assert!(!config.cdc_enabled);
1025 }
1026
1027 #[test]
1030 fn test_config_error_is_std_error() {
1031 let err = ConfigError::ZeroMemoryLimit;
1032 let dyn_err: &dyn std::error::Error = &err;
1034 assert!(dyn_err.source().is_none());
1035 assert!(!dyn_err.to_string().is_empty());
1036 }
1037
1038 #[test]
1041 fn test_validate_accepts_nonzero_memory_limit() {
1042 let config = Config::in_memory().with_memory_limit(1);
1043 assert!(config.validate().is_ok());
1044 }
1045
1046 #[test]
1047 fn test_validate_accepts_none_memory_limit() {
1048 let config = Config::in_memory();
1049 assert!(config.memory_limit.is_none());
1050 assert!(config.validate().is_ok());
1051 }
1052
1053 #[test]
1056 fn test_durability_mode_debug() {
1057 let sync = DurabilityMode::Sync;
1058 let debug = format!("{sync:?}");
1059 assert_eq!(debug, "Sync");
1060
1061 let no_sync = DurabilityMode::NoSync;
1062 let debug = format!("{no_sync:?}");
1063 assert_eq!(debug, "NoSync");
1064 }
1065
1066 #[test]
1069 fn test_read_only_config_full() {
1070 let config = Config::read_only("/tmp/data.grafeo");
1071 assert_eq!(config.access_mode, AccessMode::ReadOnly);
1072 assert!(!config.wal_enabled);
1073 assert!(config.path.is_some());
1074 assert!(config.backward_edges);
1076 assert_eq!(config.graph_model, GraphModel::Lpg);
1077 }
1078}