1mod error;
13mod permissions;
14mod row;
15mod value;
16
17pub use error::{
18 find_best_match, levenshtein_distance, suggest_keyword, ConfigError, Error, QueryContext,
19 QueryError, Result,
20};
21pub use permissions::Permissions;
22pub use row::{ColumnDef, FromRow, FromValue, TableSchema, ToRow, ToValue};
23pub use value::{ColumnType, Value};
24
25#[derive(
27 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
28)]
29pub struct PageId(pub u64);
30
31impl PageId {
32 pub const INVALID: PageId = PageId(u64::MAX);
33
34 pub fn is_valid(&self) -> bool {
35 *self != Self::INVALID
36 }
37}
38
39impl From<u64> for PageId {
40 fn from(val: u64) -> Self {
41 PageId(val)
42 }
43}
44
45impl From<PageId> for u64 {
46 fn from(val: PageId) -> Self {
47 val.0
48 }
49}
50
51#[derive(
53 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
54)]
55pub struct TransactionId(pub u64);
56
57impl TransactionId {
58 pub const NONE: TransactionId = TransactionId(0);
59
60 pub fn next(&self) -> TransactionId {
61 TransactionId(self.0 + 1)
62 }
63}
64
65impl From<u64> for TransactionId {
66 fn from(val: u64) -> Self {
67 TransactionId(val)
68 }
69}
70
71impl From<TransactionId> for u64 {
72 fn from(val: TransactionId) -> Self {
73 val.0
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
79pub struct Lsn(pub u64);
80
81impl Lsn {
82 pub const ZERO: Lsn = Lsn(0);
83
84 pub fn next(&self) -> Lsn {
85 Lsn(self.0 + 1)
86 }
87}
88
89impl From<u64> for Lsn {
90 fn from(val: u64) -> Self {
91 Lsn(val)
92 }
93}
94
95#[derive(Debug, Clone, Default)]
97pub enum EncryptionConfig {
98 #[default]
100 None,
101 Password(String),
103 Key([u8; 32]),
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
109pub enum CompressionType {
110 #[default]
112 None,
113 Lz4,
115 Zstd { level: i32 },
118}
119
120impl CompressionType {
121 pub fn zstd_default() -> Self {
123 CompressionType::Zstd { level: 3 }
124 }
125
126 pub fn zstd_high() -> Self {
128 CompressionType::Zstd { level: 9 }
129 }
130
131 pub fn zstd_max() -> Self {
133 CompressionType::Zstd { level: 19 }
134 }
135
136 pub fn is_enabled(&self) -> bool {
138 !matches!(self, CompressionType::None)
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct CompressionConfig {
145 pub compression_type: CompressionType,
147 pub threshold: usize,
150}
151
152impl Default for CompressionConfig {
153 fn default() -> Self {
154 CompressionConfig {
155 compression_type: CompressionType::None,
156 threshold: 512,
157 }
158 }
159}
160
161impl CompressionConfig {
162 pub fn none() -> Self {
164 Self::default()
165 }
166
167 pub fn lz4() -> Self {
169 CompressionConfig {
170 compression_type: CompressionType::Lz4,
171 threshold: 512,
172 }
173 }
174
175 pub fn zstd() -> Self {
177 CompressionConfig {
178 compression_type: CompressionType::zstd_default(),
179 threshold: 512,
180 }
181 }
182
183 pub fn zstd_level(level: i32) -> Self {
185 CompressionConfig {
186 compression_type: CompressionType::Zstd { level },
187 threshold: 512,
188 }
189 }
190
191 pub fn with_threshold(mut self, threshold: usize) -> Self {
193 self.threshold = threshold;
194 self
195 }
196
197 pub fn is_enabled(&self) -> bool {
199 self.compression_type.is_enabled()
200 }
201
202 pub fn validate(&self) -> std::result::Result<(), crate::ConfigError> {
211 use crate::ConfigError;
212
213 const MIN_THRESHOLD: u64 = 64; const MAX_THRESHOLD: u64 = 65536; const MIN_ZSTD_LEVEL: i32 = 1;
216 const MAX_ZSTD_LEVEL: i32 = 22;
217
218 let threshold = self.threshold as u64;
220 if threshold < MIN_THRESHOLD {
221 return Err(ConfigError::ValueTooLow {
222 field: "threshold".to_string(),
223 min: MIN_THRESHOLD,
224 actual: threshold,
225 });
226 }
227 if threshold > MAX_THRESHOLD {
228 return Err(ConfigError::ValueTooHigh {
229 field: "threshold".to_string(),
230 max: MAX_THRESHOLD,
231 actual: threshold,
232 });
233 }
234
235 if let CompressionType::Zstd { level } = self.compression_type {
237 if level < MIN_ZSTD_LEVEL {
238 return Err(ConfigError::InvalidValue {
239 field: "compression_type.level".to_string(),
240 reason: format!(
241 "ZSTD compression level {} is too low (minimum: {})",
242 level, MIN_ZSTD_LEVEL
243 ),
244 });
245 }
246 if level > MAX_ZSTD_LEVEL {
247 return Err(ConfigError::InvalidValue {
248 field: "compression_type.level".to_string(),
249 reason: format!(
250 "ZSTD compression level {} is too high (maximum: {})",
251 level, MAX_ZSTD_LEVEL
252 ),
253 });
254 }
255 }
256
257 Ok(())
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
263pub enum WalSyncMode {
264 #[default]
266 Immediate,
267 GroupCommit,
269 NoSync,
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
280pub enum EvictionPolicyType {
281 #[default]
283 Clock,
284 Lru2,
286 Lirs,
288}
289
290#[derive(Debug, Clone)]
292pub struct TransactionConfig {
293 pub timeout_ms: Option<u64>,
295 pub warn_after_ms: Option<u64>,
297}
298
299impl Default for TransactionConfig {
300 fn default() -> Self {
301 TransactionConfig {
302 timeout_ms: Some(30_000), warn_after_ms: Some(10_000), }
305 }
306}
307
308impl TransactionConfig {
309 pub fn new() -> Self {
313 Self::default()
314 }
315
316 pub fn no_limits() -> Self {
318 TransactionConfig {
319 timeout_ms: None,
320 warn_after_ms: None,
321 }
322 }
323
324 pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
326 self.timeout_ms = Some(timeout_ms);
327 self
328 }
329
330 pub fn with_warning(mut self, warn_after_ms: u64) -> Self {
332 self.warn_after_ms = Some(warn_after_ms);
333 self
334 }
335
336 pub fn with_timeout_and_warning(timeout_ms: u64, warn_after_ms: u64) -> Self {
338 TransactionConfig {
339 timeout_ms: Some(timeout_ms),
340 warn_after_ms: Some(warn_after_ms),
341 }
342 }
343
344 pub fn validate(&self) -> std::result::Result<(), crate::ConfigError> {
354 use crate::ConfigError;
355
356 const MIN_MS: u64 = 100; const MAX_MS: u64 = 3_600_000; if let Some(timeout) = self.timeout_ms {
361 if timeout < MIN_MS {
362 return Err(ConfigError::ValueTooLow {
363 field: "timeout_ms".to_string(),
364 min: MIN_MS,
365 actual: timeout,
366 });
367 }
368 if timeout > MAX_MS {
369 return Err(ConfigError::ValueTooHigh {
370 field: "timeout_ms".to_string(),
371 max: MAX_MS,
372 actual: timeout,
373 });
374 }
375 }
376
377 if let Some(warn_after) = self.warn_after_ms {
379 if warn_after < MIN_MS {
380 return Err(ConfigError::ValueTooLow {
381 field: "warn_after_ms".to_string(),
382 min: MIN_MS,
383 actual: warn_after,
384 });
385 }
386 if warn_after > MAX_MS {
387 return Err(ConfigError::ValueTooHigh {
388 field: "warn_after_ms".to_string(),
389 max: MAX_MS,
390 actual: warn_after,
391 });
392 }
393 }
394
395 if let (Some(timeout), Some(warn_after)) = (self.timeout_ms, self.warn_after_ms) {
397 if warn_after >= timeout {
398 return Err(ConfigError::InvalidValue {
399 field: "warn_after_ms".to_string(),
400 reason: format!(
401 "warn_after_ms ({}) must be less than timeout_ms ({})",
402 warn_after, timeout
403 ),
404 });
405 }
406 }
407
408 Ok(())
409 }
410}
411
412#[derive(Debug, Clone)]
414pub struct WalGroupCommitConfig {
415 pub sync_mode: WalSyncMode,
417 pub group_commit_interval_ms: u64,
420 pub group_commit_max_batch: usize,
423}
424
425impl Default for WalGroupCommitConfig {
426 fn default() -> Self {
427 WalGroupCommitConfig {
428 sync_mode: WalSyncMode::Immediate,
429 group_commit_interval_ms: 10, group_commit_max_batch: 1000, }
432 }
433}
434
435impl WalGroupCommitConfig {
436 pub fn new() -> Self {
438 Self::default()
439 }
440
441 pub fn sync_mode(mut self, mode: WalSyncMode) -> Self {
443 self.sync_mode = mode;
444 self
445 }
446
447 pub fn group_commit_interval_ms(mut self, ms: u64) -> Self {
449 self.group_commit_interval_ms = ms;
450 self
451 }
452
453 pub fn group_commit_max_batch(mut self, max: usize) -> Self {
455 self.group_commit_max_batch = max;
456 self
457 }
458
459 pub fn immediate() -> Self {
461 Self {
462 sync_mode: WalSyncMode::Immediate,
463 ..Default::default()
464 }
465 }
466
467 pub fn group_commit() -> Self {
469 Self {
470 sync_mode: WalSyncMode::GroupCommit,
471 ..Default::default()
472 }
473 }
474
475 pub fn no_sync() -> Self {
477 Self {
478 sync_mode: WalSyncMode::NoSync,
479 ..Default::default()
480 }
481 }
482
483 pub fn validate(&self) -> std::result::Result<(), crate::ConfigError> {
492 use crate::ConfigError;
493
494 const MIN_INTERVAL_MS: u64 = 1;
495 const MAX_INTERVAL_MS: u64 = 1000;
496 const MIN_BATCH: u64 = 1;
497 const MAX_BATCH: u64 = 100_000;
498
499 if self.group_commit_interval_ms < MIN_INTERVAL_MS {
501 return Err(ConfigError::ValueTooLow {
502 field: "group_commit_interval_ms".to_string(),
503 min: MIN_INTERVAL_MS,
504 actual: self.group_commit_interval_ms,
505 });
506 }
507 if self.group_commit_interval_ms > MAX_INTERVAL_MS {
508 return Err(ConfigError::ValueTooHigh {
509 field: "group_commit_interval_ms".to_string(),
510 max: MAX_INTERVAL_MS,
511 actual: self.group_commit_interval_ms,
512 });
513 }
514
515 let batch_size = self.group_commit_max_batch as u64;
517 if batch_size < MIN_BATCH {
518 return Err(ConfigError::ValueTooLow {
519 field: "group_commit_max_batch".to_string(),
520 min: MIN_BATCH,
521 actual: batch_size,
522 });
523 }
524 if batch_size > MAX_BATCH {
525 return Err(ConfigError::ValueTooHigh {
526 field: "group_commit_max_batch".to_string(),
527 max: MAX_BATCH,
528 actual: batch_size,
529 });
530 }
531
532 Ok(())
533 }
534}
535
536#[derive(Debug, Clone)]
538pub struct StorageLimitsConfig {
539 pub max_database_size: Option<u64>,
541 pub max_wal_size: Option<u64>,
543 pub safety_margin_percent: u8,
545}
546
547impl Default for StorageLimitsConfig {
548 fn default() -> Self {
549 Self {
550 max_database_size: None,
551 max_wal_size: None,
552 safety_margin_percent: 5,
553 }
554 }
555}
556
557impl StorageLimitsConfig {
558 pub fn new() -> Self {
559 Self::default()
560 }
561
562 pub fn with_max_database_size(mut self, size: u64) -> Self {
563 self.max_database_size = Some(size);
564 self
565 }
566
567 pub fn with_max_database_size_mb(mut self, mb: u64) -> Self {
568 self.max_database_size = Some(mb * 1024 * 1024);
569 self
570 }
571
572 pub fn with_max_wal_size(mut self, size: u64) -> Self {
573 self.max_wal_size = Some(size);
574 self
575 }
576
577 pub fn with_safety_margin_percent(mut self, percent: u8) -> Self {
578 self.safety_margin_percent = percent.min(50); self
580 }
581
582 pub fn effective_database_limit(&self) -> Option<u64> {
584 self.max_database_size.map(|limit| {
585 let margin = (limit as f64 * (self.safety_margin_percent as f64 / 100.0)) as u64;
586 limit.saturating_sub(margin)
587 })
588 }
589
590 pub fn validate(&self) -> std::result::Result<(), crate::ConfigError> {
600 use crate::ConfigError;
601
602 const MIN_DATABASE_SIZE: u64 = 1024 * 1024; const MIN_WAL_SIZE: u64 = 64 * 1024; const MAX_SAFETY_MARGIN: u64 = 50; if let Some(size) = self.max_database_size {
608 if size < MIN_DATABASE_SIZE {
609 return Err(ConfigError::ValueTooLow {
610 field: "max_database_size".to_string(),
611 min: MIN_DATABASE_SIZE,
612 actual: size,
613 });
614 }
615 }
616
617 if let Some(size) = self.max_wal_size {
619 if size < MIN_WAL_SIZE {
620 return Err(ConfigError::ValueTooLow {
621 field: "max_wal_size".to_string(),
622 min: MIN_WAL_SIZE,
623 actual: size,
624 });
625 }
626 }
627
628 let margin = self.safety_margin_percent as u64;
630 if margin > MAX_SAFETY_MARGIN {
631 return Err(ConfigError::ValueTooHigh {
632 field: "safety_margin_percent".to_string(),
633 max: MAX_SAFETY_MARGIN,
634 actual: margin,
635 });
636 }
637
638 Ok(())
639 }
640}
641
642#[derive(Debug, Clone)]
644pub struct Config {
645 pub page_size: usize,
647 pub buffer_pool_pages: usize,
649 pub path: std::path::PathBuf,
651 pub create_if_missing: bool,
653 pub enable_wal: bool,
655 pub sync_on_commit: bool,
658 pub encryption: EncryptionConfig,
660 pub wal_config: WalGroupCommitConfig,
662 pub compression: CompressionConfig,
664 pub eviction_policy: EvictionPolicyType,
666 pub storage_limits: StorageLimitsConfig,
668 pub api_key: Option<String>,
670}
671
672impl Default for Config {
673 fn default() -> Self {
674 Config {
675 page_size: 4096,
676 buffer_pool_pages: 16384,
677 path: std::path::PathBuf::from("featherdb.db"),
678 create_if_missing: true,
679 enable_wal: true,
680 sync_on_commit: true,
681 encryption: EncryptionConfig::None,
682 wal_config: WalGroupCommitConfig::default(),
683 compression: CompressionConfig::default(),
684 eviction_policy: EvictionPolicyType::default(),
685 storage_limits: StorageLimitsConfig::default(),
686 api_key: None,
687 }
688 }
689}
690
691impl Config {
692 pub fn new<P: Into<std::path::PathBuf>>(path: P) -> Self {
693 Config {
694 path: path.into(),
695 ..Default::default()
696 }
697 }
698
699 pub fn page_size(mut self, size: usize) -> Self {
700 self.page_size = size;
701 self
702 }
703
704 pub fn buffer_pool_size_mb(mut self, mb: usize) -> Self {
705 self.buffer_pool_pages = (mb * 1024 * 1024) / self.page_size;
706 self
707 }
708
709 pub fn create_if_missing(mut self, create: bool) -> Self {
710 self.create_if_missing = create;
711 self
712 }
713
714 pub fn with_sync_on_commit(mut self, sync: bool) -> Self {
719 self.sync_on_commit = sync;
720 self
721 }
722
723 pub fn with_password<S: Into<String>>(mut self, password: S) -> Self {
725 self.encryption = EncryptionConfig::Password(password.into());
726 self
727 }
728
729 pub fn with_key(mut self, key: [u8; 32]) -> Self {
731 self.encryption = EncryptionConfig::Key(key);
732 self
733 }
734
735 pub fn is_encrypted(&self) -> bool {
737 !matches!(self.encryption, EncryptionConfig::None)
738 }
739
740 pub fn wal_config(mut self, config: WalGroupCommitConfig) -> Self {
742 self.wal_config = config;
743 self
744 }
745
746 pub fn with_group_commit(mut self) -> Self {
748 self.wal_config = WalGroupCommitConfig::group_commit();
749 self
750 }
751
752 pub fn group_commit_interval_ms(mut self, ms: u64) -> Self {
754 self.wal_config.group_commit_interval_ms = ms;
755 self
756 }
757
758 pub fn group_commit_max_batch(mut self, max: usize) -> Self {
760 self.wal_config.group_commit_max_batch = max;
761 self
762 }
763
764 pub fn with_compression(mut self, compression: CompressionConfig) -> Self {
766 self.compression = compression;
767 self
768 }
769
770 pub fn with_lz4_compression(mut self) -> Self {
772 self.compression = CompressionConfig::lz4();
773 self
774 }
775
776 pub fn with_zstd_compression(mut self) -> Self {
778 self.compression = CompressionConfig::zstd();
779 self
780 }
781
782 pub fn with_zstd_compression_level(mut self, level: i32) -> Self {
784 self.compression = CompressionConfig::zstd_level(level);
785 self
786 }
787
788 pub fn compression_threshold(mut self, threshold: usize) -> Self {
790 self.compression.threshold = threshold;
791 self
792 }
793
794 pub fn is_compressed(&self) -> bool {
796 self.compression.is_enabled()
797 }
798
799 pub fn with_eviction_policy(mut self, policy: EvictionPolicyType) -> Self {
810 self.eviction_policy = policy;
811 self
812 }
813
814 pub fn with_lru2_eviction(mut self) -> Self {
816 self.eviction_policy = EvictionPolicyType::Lru2;
817 self
818 }
819
820 pub fn with_lirs_eviction(mut self) -> Self {
822 self.eviction_policy = EvictionPolicyType::Lirs;
823 self
824 }
825
826 pub fn with_clock_eviction(mut self) -> Self {
828 self.eviction_policy = EvictionPolicyType::Clock;
829 self
830 }
831
832 pub fn with_storage_limits(mut self, config: StorageLimitsConfig) -> Self {
834 self.storage_limits = config;
835 self
836 }
837
838 pub fn with_max_database_size(mut self, size: u64) -> Self {
840 self.storage_limits.max_database_size = Some(size);
841 self
842 }
843
844 pub fn with_max_database_size_mb(mut self, mb: u64) -> Self {
846 self.storage_limits.max_database_size = Some(mb * 1024 * 1024);
847 self
848 }
849
850 pub fn with_api_key<S: Into<String>>(mut self, key: S) -> Self {
855 self.api_key = Some(key.into());
856 self
857 }
858
859 pub fn validate(&self) -> std::result::Result<(), crate::ConfigError> {
870 use crate::ConfigError;
871
872 const MIN_PAGE_SIZE: u64 = 512;
873 const MAX_PAGE_SIZE: u64 = 65536; const MIN_BUFFER_PAGES: u64 = 16;
875
876 let page_size = self.page_size as u64;
878 if !page_size.is_power_of_two() {
879 return Err(ConfigError::InvalidValue {
880 field: "page_size".to_string(),
881 reason: format!("page_size must be a power of 2, got {}", page_size),
882 });
883 }
884
885 if page_size < MIN_PAGE_SIZE {
886 return Err(ConfigError::ValueTooLow {
887 field: "page_size".to_string(),
888 min: MIN_PAGE_SIZE,
889 actual: page_size,
890 });
891 }
892 if page_size > MAX_PAGE_SIZE {
893 return Err(ConfigError::ValueTooHigh {
894 field: "page_size".to_string(),
895 max: MAX_PAGE_SIZE,
896 actual: page_size,
897 });
898 }
899
900 let buffer_pages = self.buffer_pool_pages as u64;
902 if buffer_pages < MIN_BUFFER_PAGES {
903 return Err(ConfigError::ValueTooLow {
904 field: "buffer_pool_pages".to_string(),
905 min: MIN_BUFFER_PAGES,
906 actual: buffer_pages,
907 });
908 }
909
910 self.wal_config.validate()?;
912 self.compression.validate()?;
913 self.storage_limits.validate()?;
914
915 Ok(())
916 }
917}
918
919pub mod constants {
921 pub const MAGIC: &[u8; 10] = b"FEATHERDB\0";
923
924 pub const FORMAT_VERSION: u32 = 1;
926
927 pub const DEFAULT_PAGE_SIZE: usize = 4096;
929
930 pub const SUPERBLOCK_SIZE: usize = 512;
932
933 pub const PAGE_HEADER_SIZE: usize = 64;
935
936 pub const SLOT_SIZE: usize = 6;
938
939 pub const SCHEMA_ROOT_PAGE: u64 = 1;
941
942 pub const FREELIST_ROOT_PAGE: u64 = 2;
944
945 pub const FIRST_DATA_PAGE: u64 = 3;
947}
948
949#[cfg(test)]
950mod tests {
951 use super::*;
952
953 #[test]
954 fn test_transaction_config_default() {
955 let config = TransactionConfig::default();
956 assert_eq!(config.timeout_ms, Some(30_000));
958 assert_eq!(config.warn_after_ms, Some(10_000));
959 }
960
961 #[test]
962 fn test_transaction_config_new() {
963 let config = TransactionConfig::new();
964 assert_eq!(config.timeout_ms, Some(30_000));
966 assert_eq!(config.warn_after_ms, Some(10_000));
967 }
968
969 #[test]
970 fn test_transaction_config_no_limits() {
971 let config = TransactionConfig::no_limits();
972 assert_eq!(config.timeout_ms, None);
973 assert_eq!(config.warn_after_ms, None);
974 }
975
976 #[test]
977 fn test_transaction_config_with_timeout() {
978 let config = TransactionConfig::no_limits().with_timeout(5000);
980 assert_eq!(config.timeout_ms, Some(5000));
981 assert_eq!(config.warn_after_ms, None);
982
983 let config = TransactionConfig::new().with_timeout(5000);
985 assert_eq!(config.timeout_ms, Some(5000));
986 assert_eq!(config.warn_after_ms, Some(10_000)); }
988
989 #[test]
990 fn test_transaction_config_with_warning() {
991 let config = TransactionConfig::no_limits().with_warning(1000);
993 assert_eq!(config.timeout_ms, None);
994 assert_eq!(config.warn_after_ms, Some(1000));
995
996 let config = TransactionConfig::new().with_warning(1000);
998 assert_eq!(config.timeout_ms, Some(30_000)); assert_eq!(config.warn_after_ms, Some(1000));
1000 }
1001
1002 #[test]
1003 fn test_transaction_config_with_timeout_and_warning() {
1004 let config = TransactionConfig::with_timeout_and_warning(5000, 1000);
1005 assert_eq!(config.timeout_ms, Some(5000));
1006 assert_eq!(config.warn_after_ms, Some(1000));
1007 }
1008
1009 #[test]
1010 fn test_transaction_config_builder_pattern() {
1011 let config = TransactionConfig::new()
1012 .with_timeout(10000)
1013 .with_warning(2000);
1014 assert_eq!(config.timeout_ms, Some(10000));
1015 assert_eq!(config.warn_after_ms, Some(2000));
1016 }
1017
1018 #[test]
1019 fn test_transaction_config_clone() {
1020 let config1 = TransactionConfig::new().with_timeout(5000);
1021 let config2 = config1.clone();
1022 assert_eq!(config1.timeout_ms, config2.timeout_ms);
1023 assert_eq!(config1.warn_after_ms, config2.warn_after_ms);
1024 }
1025
1026 #[test]
1029 fn test_transaction_config_validate_success() {
1030 let config = TransactionConfig::default();
1032 assert!(config.validate().is_ok());
1033
1034 let config = TransactionConfig::new()
1036 .with_timeout(5000)
1037 .with_warning(2000);
1038 assert!(config.validate().is_ok());
1039
1040 let config = TransactionConfig::no_limits();
1042 assert!(config.validate().is_ok());
1043 }
1044
1045 #[test]
1046 fn test_transaction_config_validate_timeout_too_low() {
1047 let config = TransactionConfig::new().with_timeout(50); let err = config.validate().unwrap_err();
1049 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1050 assert!(err.to_string().contains("timeout_ms"));
1051 assert!(err.to_string().contains("100"));
1052 }
1053
1054 #[test]
1055 fn test_transaction_config_validate_timeout_too_high() {
1056 let config = TransactionConfig::new().with_timeout(4_000_000); let err = config.validate().unwrap_err();
1058 assert!(matches!(err, ConfigError::ValueTooHigh { .. }));
1059 assert!(err.to_string().contains("timeout_ms"));
1060 assert!(err.to_string().contains("3600000"));
1061 }
1062
1063 #[test]
1064 fn test_transaction_config_validate_warn_too_low() {
1065 let config = TransactionConfig::new().with_warning(50); let err = config.validate().unwrap_err();
1067 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1068 assert!(err.to_string().contains("warn_after_ms"));
1069 }
1070
1071 #[test]
1072 fn test_transaction_config_validate_warn_too_high() {
1073 let config = TransactionConfig::new().with_warning(4_000_000); let err = config.validate().unwrap_err();
1075 assert!(matches!(err, ConfigError::ValueTooHigh { .. }));
1076 assert!(err.to_string().contains("warn_after_ms"));
1077 }
1078
1079 #[test]
1080 fn test_transaction_config_validate_warn_greater_than_timeout() {
1081 let config = TransactionConfig::with_timeout_and_warning(5000, 6000); let err = config.validate().unwrap_err();
1083 assert!(matches!(err, ConfigError::InvalidValue { .. }));
1084 assert!(err.to_string().contains("warn_after_ms"));
1085 assert!(err.to_string().contains("must be less than timeout_ms"));
1086 }
1087
1088 #[test]
1089 fn test_transaction_config_validate_warn_equal_timeout() {
1090 let config = TransactionConfig::with_timeout_and_warning(5000, 5000); let err = config.validate().unwrap_err();
1092 assert!(matches!(err, ConfigError::InvalidValue { .. }));
1093 }
1094
1095 #[test]
1096 fn test_wal_config_validate_success() {
1097 let config = WalGroupCommitConfig::default();
1098 assert!(config.validate().is_ok());
1099
1100 let config = WalGroupCommitConfig::new()
1101 .group_commit_interval_ms(50)
1102 .group_commit_max_batch(500);
1103 assert!(config.validate().is_ok());
1104 }
1105
1106 #[test]
1107 fn test_wal_config_validate_interval_too_low() {
1108 let config = WalGroupCommitConfig::new().group_commit_interval_ms(0); let err = config.validate().unwrap_err();
1110 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1111 assert!(err.to_string().contains("group_commit_interval_ms"));
1112 }
1113
1114 #[test]
1115 fn test_wal_config_validate_interval_too_high() {
1116 let config = WalGroupCommitConfig::new().group_commit_interval_ms(2000); let err = config.validate().unwrap_err();
1118 assert!(matches!(err, ConfigError::ValueTooHigh { .. }));
1119 assert!(err.to_string().contains("group_commit_interval_ms"));
1120 }
1121
1122 #[test]
1123 fn test_wal_config_validate_batch_too_high() {
1124 let config = WalGroupCommitConfig::new().group_commit_max_batch(200_000); let err = config.validate().unwrap_err();
1126 assert!(matches!(err, ConfigError::ValueTooHigh { .. }));
1127 assert!(err.to_string().contains("group_commit_max_batch"));
1128 }
1129
1130 #[test]
1131 fn test_compression_config_validate_success() {
1132 assert!(CompressionConfig::default().validate().is_ok());
1133 assert!(CompressionConfig::lz4().validate().is_ok());
1134 assert!(CompressionConfig::zstd().validate().is_ok());
1135 assert!(CompressionConfig::zstd_level(9).validate().is_ok());
1136 }
1137
1138 #[test]
1139 fn test_compression_config_validate_threshold_too_low() {
1140 let config = CompressionConfig::lz4().with_threshold(32); let err = config.validate().unwrap_err();
1142 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1143 assert!(err.to_string().contains("threshold"));
1144 }
1145
1146 #[test]
1147 fn test_compression_config_validate_threshold_too_high() {
1148 let config = CompressionConfig::lz4().with_threshold(100_000); let err = config.validate().unwrap_err();
1150 assert!(matches!(err, ConfigError::ValueTooHigh { .. }));
1151 assert!(err.to_string().contains("threshold"));
1152 }
1153
1154 #[test]
1155 fn test_compression_config_validate_zstd_level_too_low() {
1156 let config = CompressionConfig::zstd_level(0); let err = config.validate().unwrap_err();
1158 assert!(matches!(err, ConfigError::InvalidValue { .. }));
1159 assert!(err.to_string().contains("ZSTD"));
1160 assert!(err.to_string().contains("too low"));
1161 }
1162
1163 #[test]
1164 fn test_compression_config_validate_zstd_level_too_high() {
1165 let config = CompressionConfig::zstd_level(25); let err = config.validate().unwrap_err();
1167 assert!(matches!(err, ConfigError::InvalidValue { .. }));
1168 assert!(err.to_string().contains("ZSTD"));
1169 assert!(err.to_string().contains("too high"));
1170 }
1171
1172 #[test]
1173 fn test_config_validate_success() {
1174 let config = Config::default();
1175 assert!(config.validate().is_ok());
1176
1177 let config = Config::new("test.db")
1178 .page_size(8192)
1179 .buffer_pool_size_mb(128);
1180 assert!(config.validate().is_ok());
1181 }
1182
1183 #[test]
1184 fn test_config_validate_page_size_not_power_of_two() {
1185 let mut config = Config::default();
1186 config.page_size = 3000; let err = config.validate().unwrap_err();
1188 assert!(matches!(err, ConfigError::InvalidValue { .. }));
1189 assert!(err.to_string().contains("power of 2"));
1190 }
1191
1192 #[test]
1193 fn test_config_validate_page_size_too_low() {
1194 let mut config = Config::default();
1195 config.page_size = 256; let err = config.validate().unwrap_err();
1197 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1198 assert!(err.to_string().contains("page_size"));
1199 }
1200
1201 #[test]
1202 fn test_config_validate_page_size_too_high() {
1203 let mut config = Config::default();
1204 config.page_size = 128 * 1024; let err = config.validate().unwrap_err();
1206 assert!(matches!(err, ConfigError::ValueTooHigh { .. }));
1207 assert!(err.to_string().contains("page_size"));
1208 }
1209
1210 #[test]
1211 fn test_config_validate_buffer_pool_too_small() {
1212 let mut config = Config::default();
1213 config.buffer_pool_pages = 8; let err = config.validate().unwrap_err();
1215 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1216 assert!(err.to_string().contains("buffer_pool_pages"));
1217 }
1218
1219 #[test]
1220 fn test_config_validate_nested_wal_config() {
1221 let mut config = Config::default();
1222 config.wal_config.group_commit_interval_ms = 2000; let err = config.validate().unwrap_err();
1224 assert!(matches!(err, ConfigError::ValueTooHigh { .. }));
1225 assert!(err.to_string().contains("group_commit_interval_ms"));
1226 }
1227
1228 #[test]
1229 fn test_config_validate_nested_compression_config() {
1230 let mut config = Config::default();
1231 config.compression.threshold = 32; let err = config.validate().unwrap_err();
1233 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1234 assert!(err.to_string().contains("threshold"));
1235 }
1236
1237 #[test]
1240 fn test_storage_limits_config_default() {
1241 let config = StorageLimitsConfig::default();
1242 assert_eq!(config.max_database_size, None);
1243 assert_eq!(config.max_wal_size, None);
1244 assert_eq!(config.safety_margin_percent, 5);
1245 }
1246
1247 #[test]
1248 fn test_storage_limits_config_new() {
1249 let config = StorageLimitsConfig::new();
1250 assert_eq!(config.max_database_size, None);
1251 assert_eq!(config.max_wal_size, None);
1252 assert_eq!(config.safety_margin_percent, 5);
1253 }
1254
1255 #[test]
1256 fn test_storage_limits_config_with_max_database_size() {
1257 let config = StorageLimitsConfig::new().with_max_database_size(100_000_000);
1258 assert_eq!(config.max_database_size, Some(100_000_000));
1259 }
1260
1261 #[test]
1262 fn test_storage_limits_config_with_max_database_size_mb() {
1263 let config = StorageLimitsConfig::new().with_max_database_size_mb(100);
1264 assert_eq!(config.max_database_size, Some(100 * 1024 * 1024));
1265 }
1266
1267 #[test]
1268 fn test_storage_limits_config_with_max_wal_size() {
1269 let config = StorageLimitsConfig::new().with_max_wal_size(10_000_000);
1270 assert_eq!(config.max_wal_size, Some(10_000_000));
1271 }
1272
1273 #[test]
1274 fn test_storage_limits_config_with_safety_margin_percent() {
1275 let config = StorageLimitsConfig::new().with_safety_margin_percent(10);
1276 assert_eq!(config.safety_margin_percent, 10);
1277 }
1278
1279 #[test]
1280 fn test_storage_limits_config_safety_margin_capped_at_50() {
1281 let config = StorageLimitsConfig::new().with_safety_margin_percent(80);
1282 assert_eq!(config.safety_margin_percent, 50); }
1284
1285 #[test]
1286 fn test_storage_limits_config_effective_database_limit() {
1287 let config = StorageLimitsConfig::new()
1288 .with_max_database_size(100_000_000)
1289 .with_safety_margin_percent(5);
1290 assert_eq!(config.effective_database_limit(), Some(95_000_000)); }
1292
1293 #[test]
1294 fn test_storage_limits_config_effective_database_limit_none() {
1295 let config = StorageLimitsConfig::new();
1296 assert_eq!(config.effective_database_limit(), None);
1297 }
1298
1299 #[test]
1300 fn test_storage_limits_config_validate_success() {
1301 assert!(StorageLimitsConfig::default().validate().is_ok());
1303
1304 let config = StorageLimitsConfig::new()
1306 .with_max_database_size(10 * 1024 * 1024) .with_max_wal_size(1024 * 1024); assert!(config.validate().is_ok());
1309 }
1310
1311 #[test]
1312 fn test_storage_limits_config_validate_database_size_too_low() {
1313 let config = StorageLimitsConfig::new().with_max_database_size(500_000); let err = config.validate().unwrap_err();
1315 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1316 assert!(err.to_string().contains("max_database_size"));
1317 assert!(err.to_string().contains("1048576")); }
1319
1320 #[test]
1321 fn test_storage_limits_config_validate_wal_size_too_low() {
1322 let config = StorageLimitsConfig::new().with_max_wal_size(32_000); let err = config.validate().unwrap_err();
1324 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1325 assert!(err.to_string().contains("max_wal_size"));
1326 assert!(err.to_string().contains("65536")); }
1328
1329 #[test]
1330 fn test_storage_limits_config_validate_safety_margin_too_high() {
1331 let mut config = StorageLimitsConfig::new();
1332 config.safety_margin_percent = 60; let err = config.validate().unwrap_err();
1334 assert!(matches!(err, ConfigError::ValueTooHigh { .. }));
1335 assert!(err.to_string().contains("safety_margin_percent"));
1336 assert!(err.to_string().contains("50"));
1337 }
1338
1339 #[test]
1340 fn test_config_with_storage_limits() {
1341 let storage_limits = StorageLimitsConfig::new()
1342 .with_max_database_size_mb(100)
1343 .with_max_wal_size(10_000_000);
1344
1345 let config = Config::new("test.db").with_storage_limits(storage_limits);
1346 assert_eq!(
1347 config.storage_limits.max_database_size,
1348 Some(100 * 1024 * 1024)
1349 );
1350 assert_eq!(config.storage_limits.max_wal_size, Some(10_000_000));
1351 }
1352
1353 #[test]
1354 fn test_config_with_max_database_size() {
1355 let config = Config::new("test.db").with_max_database_size(50_000_000);
1356 assert_eq!(config.storage_limits.max_database_size, Some(50_000_000));
1357 }
1358
1359 #[test]
1360 fn test_config_with_max_database_size_mb() {
1361 let config = Config::new("test.db").with_max_database_size_mb(50);
1362 assert_eq!(
1363 config.storage_limits.max_database_size,
1364 Some(50 * 1024 * 1024)
1365 );
1366 }
1367
1368 #[test]
1369 fn test_config_validate_nested_storage_limits() {
1370 let mut config = Config::default();
1371 config.storage_limits.max_database_size = Some(500_000); let err = config.validate().unwrap_err();
1373 assert!(matches!(err, ConfigError::ValueTooLow { .. }));
1374 assert!(err.to_string().contains("max_database_size"));
1375 }
1376}