1use std::path::PathBuf;
4
5#[derive(Debug, Clone)]
10pub struct EngineConfig {
11 pub home: PathBuf,
15
16 pub allow_create: bool,
18
19 pub transactional: bool,
24
25 pub read_only: bool,
29
30 pub cache_size: u64,
34
35 pub lock_table_count: u32,
39
40 pub lock_timeout_ms: u64,
44
45 pub txn_timeout_ms: u64,
49
50 pub evictor_enabled: bool,
55
56 pub cleaner_enabled: bool,
61
62 pub checkpointer_enabled: bool,
67
68 pub checkpoint_bytes_interval: u64,
73
74 pub cleaner_min_utilization: u32,
79
80 pub cleaner_min_file_count: u32,
84
85 pub evictor_wakeup_interval_ms: u64,
89
90 pub cleaner_wakeup_interval_ms: u64,
94
95 pub checkpointer_wakeup_interval_ms: u64,
99
100 pub log_file_max: u64,
107
108 pub log_mem_only: bool,
112
113 pub log_checksum_read: bool,
115
116 pub log_total_buffer_bytes: u64,
120
121 pub evictor_evict_bytes: u64,
128
129 pub evictor_core_threads: u32,
131
132 pub evictor_max_threads: u32,
134
135 pub evictor_n_lru_lists: u32,
139
140 pub cleaner_min_file_utilization: u32,
148
149 pub cleaner_threads: u32,
153
154 pub cleaner_lock_timeout_ms: u64,
158
159 pub txn_serializable_isolation: bool,
164
165 pub lock_deadlock_detect: bool,
167
168 pub checkpointer_high_priority: bool,
173}
174
175impl EngineConfig {
176 pub fn new(home: impl Into<PathBuf>) -> Self {
178 Self { home: home.into(), ..Default::default() }
179 }
180
181 pub fn allow_create(mut self, allow: bool) -> Self {
183 self.allow_create = allow;
184 self
185 }
186
187 pub fn transactional(mut self, enabled: bool) -> Self {
189 self.transactional = enabled;
190 self
191 }
192
193 pub fn read_only(mut self, read_only: bool) -> Self {
195 self.read_only = read_only;
196 self
197 }
198
199 pub fn cache_size(mut self, size: u64) -> Self {
201 self.cache_size = size;
202 self
203 }
204
205 pub fn lock_table_count(mut self, count: u32) -> Self {
207 self.lock_table_count = count;
208 self
209 }
210
211 pub fn lock_timeout_ms(mut self, timeout: u64) -> Self {
213 self.lock_timeout_ms = timeout;
214 self
215 }
216
217 pub fn txn_timeout_ms(mut self, timeout: u64) -> Self {
219 self.txn_timeout_ms = timeout;
220 self
221 }
222
223 pub fn evictor_enabled(mut self, enabled: bool) -> Self {
225 self.evictor_enabled = enabled;
226 self
227 }
228
229 pub fn cleaner_enabled(mut self, enabled: bool) -> Self {
231 self.cleaner_enabled = enabled;
232 self
233 }
234
235 pub fn checkpointer_enabled(mut self, enabled: bool) -> Self {
237 self.checkpointer_enabled = enabled;
238 self
239 }
240
241 pub fn checkpoint_bytes_interval(mut self, bytes: u64) -> Self {
243 self.checkpoint_bytes_interval = bytes;
244 self
245 }
246
247 pub fn cleaner_min_utilization(mut self, percent: u32) -> Self {
249 self.cleaner_min_utilization = percent.min(100);
250 self
251 }
252
253 pub fn evictor_wakeup_interval_ms(mut self, ms: u64) -> Self {
255 self.evictor_wakeup_interval_ms = ms;
256 self
257 }
258
259 pub fn cleaner_wakeup_interval_ms(mut self, ms: u64) -> Self {
261 self.cleaner_wakeup_interval_ms = ms;
262 self
263 }
264
265 pub fn checkpointer_wakeup_interval_ms(mut self, ms: u64) -> Self {
267 self.checkpointer_wakeup_interval_ms = ms;
268 self
269 }
270
271 pub fn log_file_max(mut self, bytes: u64) -> Self {
277 self.log_file_max = bytes;
278 self
279 }
280
281 pub fn log_mem_only(mut self, mem_only: bool) -> Self {
283 self.log_mem_only = mem_only;
284 self
285 }
286
287 pub fn log_checksum_read(mut self, enabled: bool) -> Self {
289 self.log_checksum_read = enabled;
290 self
291 }
292
293 pub fn log_total_buffer_bytes(mut self, bytes: u64) -> Self {
295 self.log_total_buffer_bytes = bytes;
296 self
297 }
298
299 pub fn evictor_evict_bytes(mut self, bytes: u64) -> Self {
305 self.evictor_evict_bytes = bytes;
306 self
307 }
308
309 pub fn evictor_core_threads(mut self, n: u32) -> Self {
311 self.evictor_core_threads = n;
312 self
313 }
314
315 pub fn evictor_max_threads(mut self, n: u32) -> Self {
317 self.evictor_max_threads = n;
318 self
319 }
320
321 pub fn evictor_n_lru_lists(mut self, n: u32) -> Self {
323 self.evictor_n_lru_lists = n.clamp(1, 32);
324 self
325 }
326
327 pub fn cleaner_min_file_utilization(mut self, percent: u32) -> Self {
333 self.cleaner_min_file_utilization = percent.min(50);
334 self
335 }
336
337 pub fn cleaner_threads(mut self, n: u32) -> Self {
339 self.cleaner_threads = n.max(1);
340 self
341 }
342
343 pub fn cleaner_lock_timeout_ms(mut self, ms: u64) -> Self {
345 self.cleaner_lock_timeout_ms = ms;
346 self
347 }
348
349 pub fn txn_serializable_isolation(mut self, enabled: bool) -> Self {
355 self.txn_serializable_isolation = enabled;
356 self
357 }
358
359 pub fn lock_deadlock_detect(mut self, enabled: bool) -> Self {
361 self.lock_deadlock_detect = enabled;
362 self
363 }
364
365 pub fn checkpointer_high_priority(mut self, enabled: bool) -> Self {
371 self.checkpointer_high_priority = enabled;
372 self
373 }
374
375 pub fn validate(&self) -> Result<(), String> {
379 if self.cache_size < 1024 * 1024 {
380 return Err("cache_size must be at least 1 MB".to_string());
381 }
382
383 if self.lock_table_count == 0 {
384 return Err("lock_table_count must be at least 1".to_string());
385 }
386
387 if self.cleaner_min_utilization > 100 {
388 return Err("cleaner_min_utilization must be 0-100".to_string());
389 }
390
391 if self.cleaner_min_file_utilization > 50 {
392 return Err("cleaner_min_file_utilization must be 0-50".to_string());
393 }
394
395 if self.cleaner_threads == 0 {
396 return Err("cleaner_threads must be at least 1".to_string());
397 }
398
399 if !(1..=10_000_000).contains(&self.log_file_max) {
400 return Err(
401 "log_file_max must be between 1 MB and 1 GB".to_string()
402 );
403 }
404
405 if self.evictor_n_lru_lists == 0 || self.evictor_n_lru_lists > 32 {
406 return Err(
407 "evictor_n_lru_lists must be between 1 and 32".to_string()
408 );
409 }
410
411 if self.evictor_max_threads == 0 {
412 return Err("evictor_max_threads must be at least 1".to_string());
413 }
414
415 if self.read_only && (self.cleaner_enabled || self.checkpointer_enabled)
416 {
417 return Err(
418 "cleaner and checkpointer cannot be enabled in read-only mode"
419 .to_string(),
420 );
421 }
422
423 Ok(())
424 }
425}
426
427impl Default for EngineConfig {
428 fn default() -> Self {
429 Self {
430 home: PathBuf::from("."),
431 allow_create: true,
432 transactional: true,
433 read_only: false,
434 cache_size: 64 * 1024 * 1024, lock_table_count: 16,
436 lock_timeout_ms: 500, txn_timeout_ms: 0, evictor_enabled: true,
439 cleaner_enabled: true,
440 checkpointer_enabled: true,
441 checkpoint_bytes_interval: 20_000_000, cleaner_min_utilization: 50, cleaner_min_file_count: 5,
444 evictor_wakeup_interval_ms: 5000, cleaner_wakeup_interval_ms: 10_000, checkpointer_wakeup_interval_ms: 0, log_file_max: 10_000_000, log_mem_only: false,
450 log_checksum_read: true,
451 log_total_buffer_bytes: 0, evictor_evict_bytes: 524_288, evictor_core_threads: 1,
455 evictor_max_threads: 10,
456 evictor_n_lru_lists: 4,
457 cleaner_min_file_utilization: 5, cleaner_threads: 1,
460 cleaner_lock_timeout_ms: 500, txn_serializable_isolation: false,
463 lock_deadlock_detect: true,
464 checkpointer_high_priority: false,
466 }
467 }
468}
469
470#[cfg(test)]
471#[expect(clippy::field_reassign_with_default)]
472mod tests {
473 use super::*;
474
475 #[test]
476 fn test_default_config() {
477 let config = EngineConfig::default();
478 assert_eq!(config.home, PathBuf::from("."));
479 assert!(config.allow_create);
480 assert!(config.transactional);
481 assert!(!config.read_only);
482 assert_eq!(config.cache_size, 64 * 1024 * 1024);
483 assert_eq!(config.lock_table_count, 16);
484 assert!(config.evictor_enabled);
485 assert!(config.cleaner_enabled);
486 assert!(config.checkpointer_enabled);
487 assert_eq!(config.lock_timeout_ms, 500);
489 assert_eq!(config.txn_timeout_ms, 0);
490 assert_eq!(config.cleaner_min_utilization, 50);
491 assert_eq!(config.cleaner_min_file_utilization, 5);
492 assert_eq!(config.cleaner_threads, 1);
493 assert_eq!(config.checkpoint_bytes_interval, 20_000_000);
494 assert_eq!(config.log_file_max, 10_000_000);
495 assert!(!config.log_mem_only);
496 assert!(config.log_checksum_read);
497 assert_eq!(config.log_total_buffer_bytes, 0);
498 assert_eq!(config.evictor_evict_bytes, 524_288);
499 assert_eq!(config.evictor_core_threads, 1);
500 assert_eq!(config.evictor_max_threads, 10);
501 assert_eq!(config.evictor_n_lru_lists, 4);
502 assert!(!config.txn_serializable_isolation);
503 assert!(config.lock_deadlock_detect);
504 assert!(!config.checkpointer_high_priority);
505 }
506
507 #[test]
508 fn test_new_config() {
509 let config = EngineConfig::new("/tmp/mydb");
510 assert_eq!(config.home, PathBuf::from("/tmp/mydb"));
511 assert!(config.allow_create);
513 assert!(config.transactional);
514 }
515
516 #[test]
517 fn test_builder_pattern() {
518 let config = EngineConfig::new("/data/db")
519 .allow_create(false)
520 .transactional(true)
521 .read_only(false)
522 .cache_size(128 * 1024 * 1024)
523 .lock_table_count(32)
524 .lock_timeout_ms(10000)
525 .txn_timeout_ms(20000)
526 .evictor_enabled(true)
527 .cleaner_enabled(false)
528 .checkpointer_enabled(true)
529 .checkpoint_bytes_interval(50_000_000)
530 .cleaner_min_utilization(60)
531 .log_file_max(20_000_000)
532 .log_mem_only(false)
533 .log_checksum_read(true)
534 .evictor_evict_bytes(1_048_576)
535 .evictor_core_threads(2)
536 .evictor_max_threads(8)
537 .evictor_n_lru_lists(8)
538 .cleaner_min_file_utilization(10)
539 .cleaner_threads(2)
540 .cleaner_lock_timeout_ms(1000)
541 .txn_serializable_isolation(true)
542 .lock_deadlock_detect(true)
543 .checkpointer_high_priority(true);
544
545 assert_eq!(config.home, PathBuf::from("/data/db"));
546 assert!(!config.allow_create);
547 assert!(config.transactional);
548 assert!(!config.read_only);
549 assert_eq!(config.cache_size, 128 * 1024 * 1024);
550 assert_eq!(config.lock_table_count, 32);
551 assert_eq!(config.lock_timeout_ms, 10000);
552 assert_eq!(config.txn_timeout_ms, 20000);
553 assert!(config.evictor_enabled);
554 assert!(!config.cleaner_enabled);
555 assert!(config.checkpointer_enabled);
556 assert_eq!(config.checkpoint_bytes_interval, 50_000_000);
557 assert_eq!(config.cleaner_min_utilization, 60);
558 assert_eq!(config.log_file_max, 20_000_000);
559 assert!(!config.log_mem_only);
560 assert!(config.log_checksum_read);
561 assert_eq!(config.evictor_evict_bytes, 1_048_576);
562 assert_eq!(config.evictor_core_threads, 2);
563 assert_eq!(config.evictor_max_threads, 8);
564 assert_eq!(config.evictor_n_lru_lists, 8);
565 assert_eq!(config.cleaner_min_file_utilization, 10);
566 assert_eq!(config.cleaner_threads, 2);
567 assert_eq!(config.cleaner_lock_timeout_ms, 1000);
568 assert!(config.txn_serializable_isolation);
569 assert!(config.lock_deadlock_detect);
570 assert!(config.checkpointer_high_priority);
571 }
572
573 #[test]
574 fn test_validate_valid_config() {
575 let config = EngineConfig::default();
576 assert!(config.validate().is_ok());
577 }
578
579 #[test]
580 fn test_validate_cache_too_small() {
581 let config = EngineConfig::default().cache_size(1024);
582 let result = config.validate();
583 assert!(result.is_err());
584 assert!(result.unwrap_err().contains("cache_size"));
585 }
586
587 #[test]
588 fn test_validate_zero_lock_tables() {
589 let config = EngineConfig::default().lock_table_count(0);
590 let result = config.validate();
591 assert!(result.is_err());
592 assert!(result.unwrap_err().contains("lock_table_count"));
593 }
594
595 #[test]
596 fn test_validate_invalid_utilization() {
597 let mut config = EngineConfig::default();
598 config.cleaner_min_utilization = 150;
599 let result = config.validate();
600 assert!(result.is_err());
601 assert!(result.unwrap_err().contains("utilization"));
602 }
603
604 #[test]
605 fn test_validate_readonly_conflicts() {
606 let config =
607 EngineConfig::default().read_only(true).cleaner_enabled(true);
608 let result = config.validate();
609 assert!(result.is_err());
610 assert!(result.unwrap_err().contains("read-only"));
611
612 let config =
613 EngineConfig::default().read_only(true).checkpointer_enabled(true);
614 let result = config.validate();
615 assert!(result.is_err());
616 assert!(result.unwrap_err().contains("read-only"));
617 }
618
619 #[test]
620 fn test_cleaner_utilization_clamped() {
621 let config = EngineConfig::default().cleaner_min_utilization(150);
622 assert_eq!(config.cleaner_min_utilization, 100);
623
624 let config = EngineConfig::default().cleaner_min_utilization(50);
625 assert_eq!(config.cleaner_min_utilization, 50);
626 }
627
628 #[test]
629 fn test_readonly_config() {
630 let config = EngineConfig::new("/db")
631 .read_only(true)
632 .cleaner_enabled(false)
633 .checkpointer_enabled(false);
634 assert!(config.validate().is_ok());
635 assert!(config.read_only);
636 assert!(!config.cleaner_enabled);
637 assert!(!config.checkpointer_enabled);
638 }
639}