1use crate::error::{Result, TdbError};
12use serde::{Deserialize, Serialize};
13use std::path::{Path, PathBuf};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct StoreParams {
18 pub data_dir: PathBuf,
21 pub page_size: usize,
23 pub buffer_pool_size: usize,
25
26 pub enable_spo_index: bool,
31 pub enable_pos_index: bool,
34 pub enable_osp_index: bool,
37 pub enable_quad_indexes: bool,
40
41 pub enable_inline_values: bool,
45 pub enable_prefix_compression: bool,
50 pub dictionary_cache_size: usize,
53
54 pub enable_wal: bool,
57 pub wal_buffer_size: usize,
59 pub max_transaction_size: usize,
62 pub transaction_timeout_secs: u64,
65
66 pub enable_compression: bool,
69 pub compression_algorithm: CompressionAlgorithm,
71 pub compression_level: u32,
73
74 pub enable_bloom_filters: bool,
77 pub bloom_filter_fpr: f64,
79 pub bloom_filter_size_per_index: usize,
81
82 pub enable_query_cache: bool,
85 pub query_cache_size: usize,
87 pub enable_statistics: bool,
89 pub statistics_sample_rate: f64,
91
92 pub enable_query_monitoring: bool,
95 pub slow_query_threshold_ms: u64,
97 pub query_timeout_ms: u64,
99
100 pub enable_spatial_indexing: bool,
103 pub spatial_index_max_entries: usize,
105
106 pub enable_diagnostics: bool,
110 pub enable_metrics: bool,
113 pub enable_profiling: bool,
116
117 pub min_connections: usize,
121 pub max_connections: usize,
124 pub connection_timeout_secs: u64,
127
128 pub enable_online_backup: bool,
132 pub backup_retention_days: u32,
135 pub enable_backup_encryption: bool,
138
139 pub enable_direct_io: bool,
145 pub enable_async_io: bool,
148 pub enable_numa_awareness: bool,
151 pub enable_gpu_acceleration: bool,
154
155 pub enable_replication: bool,
159 pub replication_mode: ReplicationMode,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
166pub enum CompressionAlgorithm {
167 None,
169 Lz4,
171 Zstd,
173 Brotli,
175 Snappy,
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
181pub enum ReplicationMode {
182 None,
184 MasterSlave,
186 MasterMaster,
188}
189
190impl StoreParams {
191 pub fn new<P: AsRef<Path>>(data_dir: P) -> Self {
193 Self {
194 data_dir: data_dir.as_ref().to_path_buf(),
196 page_size: 4096,
197 buffer_pool_size: 1000,
198
199 enable_spo_index: true,
201 enable_pos_index: true,
202 enable_osp_index: true,
203 enable_quad_indexes: false,
204
205 enable_inline_values: true,
207 enable_prefix_compression: true,
208 dictionary_cache_size: 10000,
209
210 enable_wal: true,
212 wal_buffer_size: 1024 * 1024, max_transaction_size: 1_000_000,
214 transaction_timeout_secs: 60,
215
216 enable_compression: true,
218 compression_algorithm: CompressionAlgorithm::Lz4,
219 compression_level: 3,
220
221 enable_bloom_filters: true,
223 bloom_filter_fpr: 0.01,
224 bloom_filter_size_per_index: 1_000_000,
225
226 enable_query_cache: true,
228 query_cache_size: 1000,
229 enable_statistics: true,
230 statistics_sample_rate: 1.0,
231
232 enable_query_monitoring: true,
234 slow_query_threshold_ms: 1000,
235 query_timeout_ms: 30_000,
236
237 enable_spatial_indexing: true,
239 spatial_index_max_entries: 1000,
240
241 enable_diagnostics: true,
243 enable_metrics: true,
244 enable_profiling: false,
245
246 min_connections: 2,
248 max_connections: 10,
249 connection_timeout_secs: 30,
250
251 enable_online_backup: true,
253 backup_retention_days: 7,
254 enable_backup_encryption: false,
255
256 enable_direct_io: false,
258 enable_async_io: false,
259 enable_numa_awareness: false,
260 enable_gpu_acceleration: false,
261
262 enable_replication: false,
264 replication_mode: ReplicationMode::None,
265 }
266 }
267
268 pub fn validate(&self) -> Result<()> {
270 if !self.page_size.is_power_of_two() || self.page_size < 512 {
272 return Err(TdbError::InvalidConfiguration(format!(
273 "Page size must be power of 2 and >= 512, got {}",
274 self.page_size
275 )));
276 }
277
278 if self.buffer_pool_size == 0 {
280 return Err(TdbError::InvalidConfiguration(
281 "Buffer pool size must be > 0".to_string(),
282 ));
283 }
284
285 if self.bloom_filter_fpr <= 0.0 || self.bloom_filter_fpr >= 1.0 {
287 return Err(TdbError::InvalidConfiguration(format!(
288 "Bloom filter FPR must be between 0 and 1, got {}",
289 self.bloom_filter_fpr
290 )));
291 }
292
293 if self.statistics_sample_rate < 0.0 || self.statistics_sample_rate > 1.0 {
295 return Err(TdbError::InvalidConfiguration(format!(
296 "Statistics sample rate must be between 0 and 1, got {}",
297 self.statistics_sample_rate
298 )));
299 }
300
301 if self.min_connections > self.max_connections {
303 return Err(TdbError::InvalidConfiguration(format!(
304 "Min connections ({}) > max connections ({})",
305 self.min_connections, self.max_connections
306 )));
307 }
308
309 Ok(())
310 }
311
312 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
314 let json = serde_json::to_string_pretty(self)
315 .map_err(|e| TdbError::Serialization(format!("Failed to serialize config: {}", e)))?;
316 std::fs::write(path, json)?;
317 Ok(())
318 }
319
320 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
322 let json = std::fs::read_to_string(path)?;
323 let params: StoreParams = serde_json::from_str(&json)
324 .map_err(|e| TdbError::Deserialization(format!("Failed to parse config: {}", e)))?;
325 params.validate()?;
326 Ok(params)
327 }
328}
329
330pub struct StoreParamsBuilder {
332 params: StoreParams,
333}
334
335impl StoreParamsBuilder {
336 pub fn new<P: AsRef<Path>>(data_dir: P) -> Self {
338 Self {
339 params: StoreParams::new(data_dir),
340 }
341 }
342
343 pub fn from_params(params: StoreParams) -> Self {
345 Self { params }
346 }
347
348 pub fn page_size(mut self, size: usize) -> Self {
350 self.params.page_size = size;
351 self
352 }
353
354 pub fn buffer_pool_size(mut self, size: usize) -> Self {
356 self.params.buffer_pool_size = size;
357 self
358 }
359
360 pub fn with_triple_indexes(mut self, spo: bool, pos: bool, osp: bool) -> Self {
362 self.params.enable_spo_index = spo;
363 self.params.enable_pos_index = pos;
364 self.params.enable_osp_index = osp;
365 self
366 }
367
368 pub fn with_quad_indexes(mut self, enable: bool) -> Self {
370 self.params.enable_quad_indexes = enable;
371 self
372 }
373
374 pub fn with_inline_values(mut self, enable: bool) -> Self {
376 self.params.enable_inline_values = enable;
377 self
378 }
379
380 pub fn with_prefix_compression(mut self, enable: bool) -> Self {
382 self.params.enable_prefix_compression = enable;
383 self
384 }
385
386 pub fn dictionary_cache_size(mut self, size: usize) -> Self {
388 self.params.dictionary_cache_size = size;
389 self
390 }
391
392 pub fn with_wal(mut self, enable: bool) -> Self {
394 self.params.enable_wal = enable;
395 self
396 }
397
398 pub fn wal_buffer_size(mut self, size: usize) -> Self {
400 self.params.wal_buffer_size = size;
401 self
402 }
403
404 pub fn max_transaction_size(mut self, size: usize) -> Self {
406 self.params.max_transaction_size = size;
407 self
408 }
409
410 pub fn transaction_timeout(mut self, seconds: u64) -> Self {
412 self.params.transaction_timeout_secs = seconds;
413 self
414 }
415
416 pub fn with_compression(mut self, algorithm: CompressionAlgorithm, level: u32) -> Self {
418 self.params.enable_compression = algorithm != CompressionAlgorithm::None;
419 self.params.compression_algorithm = algorithm;
420 self.params.compression_level = level;
421 self
422 }
423
424 pub fn with_bloom_filters(mut self, enable: bool, fpr: f64, size: usize) -> Self {
426 self.params.enable_bloom_filters = enable;
427 self.params.bloom_filter_fpr = fpr;
428 self.params.bloom_filter_size_per_index = size;
429 self
430 }
431
432 pub fn with_query_cache(mut self, enable: bool, size: usize) -> Self {
434 self.params.enable_query_cache = enable;
435 self.params.query_cache_size = size;
436 self
437 }
438
439 pub fn with_statistics(mut self, enable: bool, sample_rate: f64) -> Self {
441 self.params.enable_statistics = enable;
442 self.params.statistics_sample_rate = sample_rate;
443 self
444 }
445
446 pub fn with_query_monitoring(
448 mut self,
449 enable: bool,
450 slow_threshold_ms: u64,
451 timeout_ms: u64,
452 ) -> Self {
453 self.params.enable_query_monitoring = enable;
454 self.params.slow_query_threshold_ms = slow_threshold_ms;
455 self.params.query_timeout_ms = timeout_ms;
456 self
457 }
458
459 pub fn with_spatial_indexing(mut self, enable: bool, max_entries: usize) -> Self {
461 self.params.enable_spatial_indexing = enable;
462 self.params.spatial_index_max_entries = max_entries;
463 self
464 }
465
466 pub fn with_production_features(
468 mut self,
469 diagnostics: bool,
470 metrics: bool,
471 profiling: bool,
472 ) -> Self {
473 self.params.enable_diagnostics = diagnostics;
474 self.params.enable_metrics = metrics;
475 self.params.enable_profiling = profiling;
476 self
477 }
478
479 pub fn with_connection_pool(mut self, min: usize, max: usize, timeout_secs: u64) -> Self {
481 self.params.min_connections = min;
482 self.params.max_connections = max;
483 self.params.connection_timeout_secs = timeout_secs;
484 self
485 }
486
487 pub fn with_backup(mut self, enable_online: bool, retention_days: u32, encrypt: bool) -> Self {
489 self.params.enable_online_backup = enable_online;
490 self.params.backup_retention_days = retention_days;
491 self.params.enable_backup_encryption = encrypt;
492 self
493 }
494
495 pub fn with_performance_features(
497 mut self,
498 direct_io: bool,
499 async_io: bool,
500 numa: bool,
501 gpu: bool,
502 ) -> Self {
503 self.params.enable_direct_io = direct_io;
504 self.params.enable_async_io = async_io;
505 self.params.enable_numa_awareness = numa;
506 self.params.enable_gpu_acceleration = gpu;
507 self
508 }
509
510 pub fn with_replication(mut self, mode: ReplicationMode) -> Self {
512 self.params.enable_replication = mode != ReplicationMode::None;
513 self.params.replication_mode = mode;
514 self
515 }
516
517 pub fn build(self) -> Result<StoreParams> {
519 self.params.validate()?;
520 Ok(self.params)
521 }
522
523 pub fn build_unchecked(self) -> StoreParams {
525 self.params
526 }
527}
528
529pub struct StorePresets;
531
532impl StorePresets {
533 pub fn development<P: AsRef<Path>>(data_dir: P) -> StoreParamsBuilder {
535 StoreParamsBuilder::new(data_dir)
536 .buffer_pool_size(100)
537 .with_compression(CompressionAlgorithm::None, 0)
538 .with_statistics(false, 0.0)
539 .with_production_features(false, false, false)
540 .with_performance_features(false, false, false, false)
541 }
542
543 pub fn production<P: AsRef<Path>>(data_dir: P) -> StoreParamsBuilder {
545 StoreParamsBuilder::new(data_dir)
546 .buffer_pool_size(10000)
547 .with_compression(CompressionAlgorithm::Lz4, 3)
548 .with_statistics(true, 1.0)
549 .with_production_features(true, true, false)
550 .with_backup(true, 30, true)
551 .with_connection_pool(5, 50, 60)
552 }
553
554 pub fn performance<P: AsRef<Path>>(data_dir: P) -> StoreParamsBuilder {
556 StoreParamsBuilder::new(data_dir)
557 .buffer_pool_size(50000)
558 .with_compression(CompressionAlgorithm::Snappy, 1)
559 .with_bloom_filters(true, 0.001, 10_000_000)
560 .with_performance_features(true, true, true, true)
561 .with_query_cache(true, 10000)
562 }
563
564 pub fn minimal<P: AsRef<Path>>(data_dir: P) -> StoreParamsBuilder {
566 StoreParamsBuilder::new(data_dir)
567 .buffer_pool_size(10)
568 .with_compression(CompressionAlgorithm::None, 0)
569 .with_bloom_filters(false, 0.01, 0)
570 .with_query_cache(false, 0)
571 .with_statistics(false, 0.0)
572 .with_production_features(false, false, false)
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use std::env;
580 use std::path::PathBuf;
581
582 fn test_store_path() -> PathBuf {
586 env::temp_dir().join("oxirs_tdb_store_params_test")
587 }
588
589 #[test]
590 fn test_store_params_new() {
591 let params = StoreParams::new(test_store_path());
592 assert_eq!(params.page_size, 4096);
593 assert_eq!(params.buffer_pool_size, 1000);
594 assert!(params.enable_spo_index);
595 }
596
597 #[test]
598 fn test_store_params_validation() {
599 let params = StoreParams::new(test_store_path());
600 assert!(params.validate().is_ok());
601
602 let mut invalid_params = params.clone();
603 invalid_params.page_size = 1000; assert!(invalid_params.validate().is_err());
605
606 let mut invalid_params2 = params.clone();
607 invalid_params2.bloom_filter_fpr = 1.5; assert!(invalid_params2.validate().is_err());
609 }
610
611 #[test]
612 fn test_builder_basic() {
613 let params = StoreParamsBuilder::new(test_store_path())
614 .page_size(8192)
615 .buffer_pool_size(2000)
616 .build()
617 .unwrap();
618
619 assert_eq!(params.page_size, 8192);
620 assert_eq!(params.buffer_pool_size, 2000);
621 }
622
623 #[test]
624 fn test_builder_compression() {
625 let params = StoreParamsBuilder::new(test_store_path())
626 .with_compression(CompressionAlgorithm::Zstd, 5)
627 .build()
628 .unwrap();
629
630 assert!(params.enable_compression);
631 assert_eq!(params.compression_algorithm, CompressionAlgorithm::Zstd);
632 assert_eq!(params.compression_level, 5);
633 }
634
635 #[test]
636 fn test_builder_bloom_filters() {
637 let params = StoreParamsBuilder::new(test_store_path())
638 .with_bloom_filters(true, 0.001, 5_000_000)
639 .build()
640 .unwrap();
641
642 assert!(params.enable_bloom_filters);
643 assert_eq!(params.bloom_filter_fpr, 0.001);
644 assert_eq!(params.bloom_filter_size_per_index, 5_000_000);
645 }
646
647 #[test]
648 fn test_presets_development() {
649 let params = StorePresets::development(test_store_path())
650 .build()
651 .unwrap();
652 assert_eq!(params.buffer_pool_size, 100);
653 assert_eq!(params.compression_algorithm, CompressionAlgorithm::None);
654 assert!(!params.enable_statistics);
655 }
656
657 #[test]
658 fn test_presets_production() {
659 let params = StorePresets::production(test_store_path()).build().unwrap();
660 assert_eq!(params.buffer_pool_size, 10000);
661 assert!(params.enable_diagnostics);
662 assert!(params.enable_backup_encryption);
663 }
664
665 #[test]
666 fn test_presets_performance() {
667 let params = StorePresets::performance(test_store_path())
668 .build()
669 .unwrap();
670 assert_eq!(params.buffer_pool_size, 50000);
671 assert!(params.enable_direct_io);
672 assert!(params.enable_gpu_acceleration);
673 }
674
675 #[test]
676 fn test_save_load_params() {
677 let temp_dir = env::temp_dir();
678 let config_file = temp_dir.join("test_store_params.json");
679
680 let params = StoreParamsBuilder::new(&temp_dir)
681 .page_size(8192)
682 .buffer_pool_size(5000)
683 .build()
684 .unwrap();
685
686 params.save_to_file(&config_file).unwrap();
687 let loaded_params = StoreParams::load_from_file(&config_file).unwrap();
688
689 assert_eq!(params.page_size, loaded_params.page_size);
690 assert_eq!(params.buffer_pool_size, loaded_params.buffer_pool_size);
691 }
692
693 #[test]
694 fn test_replication_config() {
695 let params = StoreParamsBuilder::new(test_store_path())
696 .with_replication(ReplicationMode::MasterSlave)
697 .build()
698 .unwrap();
699
700 assert!(params.enable_replication);
701 assert_eq!(params.replication_mode, ReplicationMode::MasterSlave);
702 }
703
704 #[test]
705 fn test_connection_pool_validation() {
706 let result = StoreParamsBuilder::new(test_store_path())
707 .with_connection_pool(10, 5, 30) .build();
709
710 assert!(result.is_err());
711 }
712}