1use async_trait::async_trait;
7use sled::Db;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use tokio::sync::RwLock;
11use tracing::{debug, error, info, trace, warn};
12
13use super::backend::StateBackend;
14use super::compression::{compress, decompress, CompressionConfig};
15use crate::error::{StateError, StateResult};
16
17#[derive(Debug, Clone, Default)]
19pub struct SledBackendStats {
20 pub get_count: u64,
22 pub put_count: u64,
24 pub delete_count: u64,
26 pub flush_count: u64,
28 pub bytes_written: u64,
30 pub bytes_read: u64,
32 pub bytes_compressed: u64,
34 pub bytes_decompressed: u64,
36 pub bytes_before_compression: u64,
38 pub bytes_after_compression: u64,
40}
41
42#[derive(Debug, Clone)]
44pub struct SledConfig {
45 pub path: PathBuf,
47 pub cache_capacity: u64,
49 pub compression: CompressionConfig,
51 pub flush_every: u64,
53 pub segment_size: usize,
55}
56
57impl Default for SledConfig {
58 fn default() -> Self {
59 Self {
60 path: PathBuf::from("/tmp/sled_state"),
61 cache_capacity: 128 * 1024 * 1024, compression: CompressionConfig::balanced(),
63 flush_every: 1000,
64 segment_size: 8 * 1024 * 1024, }
66 }
67}
68
69impl SledConfig {
70 pub fn new<P: AsRef<Path>>(path: P) -> Self {
72 Self {
73 path: path.as_ref().to_path_buf(),
74 ..Default::default()
75 }
76 }
77
78 pub fn with_cache_capacity(mut self, capacity: u64) -> Self {
80 self.cache_capacity = capacity;
81 self
82 }
83
84 pub fn with_compression(mut self, config: CompressionConfig) -> Self {
86 self.compression = config;
87 self
88 }
89
90 pub fn without_compression(mut self) -> Self {
92 self.compression = CompressionConfig::none();
93 self
94 }
95
96 pub fn with_fast_compression(mut self) -> Self {
98 self.compression = CompressionConfig::fast();
99 self
100 }
101
102 pub fn with_balanced_compression(mut self) -> Self {
104 self.compression = CompressionConfig::balanced();
105 self
106 }
107
108 pub fn with_best_compression(mut self) -> Self {
110 self.compression = CompressionConfig::best_size();
111 self
112 }
113
114 pub fn with_flush_every(mut self, count: u64) -> Self {
116 self.flush_every = count;
117 self
118 }
119}
120
121pub struct SledStateBackend {
159 db: Arc<Db>,
161 config: SledConfig,
163 stats: Arc<RwLock<SledBackendStats>>,
165 op_counter: Arc<RwLock<u64>>,
167}
168
169impl SledStateBackend {
170 pub async fn open(config: SledConfig) -> StateResult<Self> {
193 info!("Opening Sled database at {:?}", config.path);
194
195 if let Some(parent) = config.path.parent() {
197 tokio::fs::create_dir_all(parent).await.map_err(|e| {
198 StateError::StorageError {
199 backend_type: "sled".to_string(),
200 details: format!("Failed to create directory: {}", e),
201 }
202 })?;
203 }
204
205 let db = sled::Config::new()
209 .path(&config.path)
210 .cache_capacity(config.cache_capacity)
211 .use_compression(false) .segment_size(config.segment_size)
213 .open()
214 .map_err(|e| StateError::StorageError {
215 backend_type: "sled".to_string(),
216 details: format!("Failed to open database: {}", e),
217 })?;
218
219 info!(
220 "Sled database opened successfully, {} keys",
221 db.len()
222 );
223
224 Ok(Self {
225 db: Arc::new(db),
226 config,
227 stats: Arc::new(RwLock::new(SledBackendStats::default())),
228 op_counter: Arc::new(RwLock::new(0)),
229 })
230 }
231
232 pub async fn temporary() -> StateResult<Self> {
253 let temp_dir = std::env::temp_dir().join(format!("sled_test_{}", uuid::Uuid::new_v4()));
254 Self::open(SledConfig::new(temp_dir)).await
255 }
256
257 pub async fn flush(&self) -> StateResult<usize> {
282 trace!("Flushing Sled database");
283
284 let flushed = tokio::task::block_in_place(|| {
285 self.db.flush().map_err(|e| StateError::StorageError {
286 backend_type: "sled".to_string(),
287 details: format!("Flush failed: {}", e),
288 })
289 })?;
290
291 let mut stats = self.stats.write().await;
292 stats.flush_count += 1;
293
294 debug!("Flushed {} bytes to disk", flushed);
295 Ok(flushed)
296 }
297
298 pub async fn stats(&self) -> SledBackendStats {
304 self.stats.read().await.clone()
305 }
306
307 pub async fn compression_ratio(&self) -> Option<f64> {
317 let stats = self.stats.read().await;
318 if stats.bytes_before_compression > 0 {
319 Some((stats.bytes_after_compression as f64 / stats.bytes_before_compression as f64) * 100.0)
320 } else {
321 None
322 }
323 }
324
325 pub async fn space_saved(&self) -> u64 {
331 let stats = self.stats.read().await;
332 stats.bytes_before_compression.saturating_sub(stats.bytes_after_compression)
333 }
334
335 pub async fn size_on_disk(&self) -> StateResult<u64> {
341 let path = &self.config.path;
342 let mut total_size = 0u64;
343
344 let entries = tokio::fs::read_dir(path).await.map_err(|e| {
345 StateError::StorageError {
346 backend_type: "sled".to_string(),
347 details: format!("Failed to read directory: {}", e),
348 }
349 })?;
350
351 let mut entries = entries;
352 while let Some(entry) = entries.next_entry().await.map_err(|e| {
353 StateError::StorageError {
354 backend_type: "sled".to_string(),
355 details: format!("Failed to read entry: {}", e),
356 }
357 })? {
358 if let Ok(metadata) = entry.metadata().await {
359 total_size += metadata.len();
360 }
361 }
362
363 Ok(total_size)
364 }
365
366 pub async fn checkpoint(&self) -> StateResult<Vec<(Vec<u8>, Vec<u8>)>> {
374 info!("Creating Sled checkpoint");
375
376 let checkpoint = tokio::task::block_in_place(|| {
377 self.db
378 .iter()
379 .map(|result| {
380 result
381 .map(|(k, v)| (k.to_vec(), v.to_vec()))
382 .map_err(|e| StateError::StorageError {
383 backend_type: "sled".to_string(),
384 details: format!("Checkpoint iteration failed: {}", e),
385 })
386 })
387 .collect::<StateResult<Vec<_>>>()
388 })?;
389
390 info!("Checkpoint created with {} entries", checkpoint.len());
391 Ok(checkpoint)
392 }
393
394 pub async fn restore(&self, checkpoint: Vec<(Vec<u8>, Vec<u8>)>) -> StateResult<()> {
407 info!("Restoring Sled from checkpoint with {} entries", checkpoint.len());
408
409 tokio::task::block_in_place(|| {
410 self.db.clear().map_err(|e| StateError::StorageError {
412 backend_type: "sled".to_string(),
413 details: format!("Clear failed: {}", e),
414 })?;
415
416 for (key, value) in checkpoint {
418 self.db.insert(key, value).map_err(|e| StateError::StorageError {
419 backend_type: "sled".to_string(),
420 details: format!("Insert failed during restore: {}", e),
421 })?;
422 }
423
424 self.db.flush().map_err(|e| StateError::StorageError {
426 backend_type: "sled".to_string(),
427 details: format!("Flush failed after restore: {}", e),
428 })?;
429
430 Ok(())
431 })?;
432
433 info!("Restore completed successfully");
434 Ok(())
435 }
436
437 async fn maybe_flush(&self) -> StateResult<()> {
439 let mut counter = self.op_counter.write().await;
440 *counter += 1;
441
442 if *counter >= self.config.flush_every {
443 *counter = 0;
444 drop(counter); self.flush().await?;
446 }
447
448 Ok(())
449 }
450}
451
452#[async_trait]
453impl StateBackend for SledStateBackend {
454 async fn get(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
455 trace!("Getting key from Sled: {:?}", key);
456
457 let result = tokio::task::block_in_place(|| {
458 self.db.get(key).map_err(|e| StateError::StorageError {
459 backend_type: "sled".to_string(),
460 details: format!("Get failed: {}", e),
461 })
462 })?;
463
464 let mut stats = self.stats.write().await;
465 stats.get_count += 1;
466
467 if let Some(compressed_value) = result {
468 let compressed_len = compressed_value.len() as u64;
469 stats.bytes_read += compressed_len;
470
471 let decompressed = decompress(&compressed_value, &self.config.compression)
473 .map_err(|e| StateError::StorageError {
474 backend_type: "sled".to_string(),
475 details: format!("Decompression failed: {}", e),
476 })?;
477
478 let decompressed_len = decompressed.len() as u64;
479 stats.bytes_decompressed += decompressed_len;
480
481 trace!(
482 "Decompressed {} bytes to {} bytes (ratio: {:.2}%)",
483 compressed_len,
484 decompressed_len,
485 (compressed_len as f64 / decompressed_len as f64) * 100.0
486 );
487
488 Ok(Some(decompressed))
489 } else {
490 Ok(None)
491 }
492 }
493
494 async fn put(&self, key: &[u8], value: &[u8]) -> StateResult<()> {
495 trace!("Putting key to Sled: {:?}, value size: {} bytes", key, value.len());
496
497 let original_len = value.len() as u64;
498
499 let compressed = compress(value, &self.config.compression)
501 .map_err(|e| StateError::StorageError {
502 backend_type: "sled".to_string(),
503 details: format!("Compression failed: {}", e),
504 })?;
505
506 let compressed_len = compressed.len() as u64;
507
508 trace!(
509 "Compressed {} bytes to {} bytes (ratio: {:.2}%)",
510 original_len,
511 compressed_len,
512 (compressed_len as f64 / original_len as f64) * 100.0
513 );
514
515 tokio::task::block_in_place(|| {
516 self.db.insert(key, compressed.as_slice()).map_err(|e| StateError::StorageError {
517 backend_type: "sled".to_string(),
518 details: format!("Put failed: {}", e),
519 })
520 })?;
521
522 let mut stats = self.stats.write().await;
523 stats.put_count += 1;
524 stats.bytes_written += (key.len() as u64 + compressed_len);
525 stats.bytes_compressed += original_len;
526 stats.bytes_before_compression += original_len;
527 stats.bytes_after_compression += compressed_len;
528 drop(stats);
529
530 self.maybe_flush().await?;
531
532 Ok(())
533 }
534
535 async fn delete(&self, key: &[u8]) -> StateResult<()> {
536 trace!("Deleting key from Sled: {:?}", key);
537
538 tokio::task::block_in_place(|| {
539 self.db.remove(key).map_err(|e| StateError::StorageError {
540 backend_type: "sled".to_string(),
541 details: format!("Delete failed: {}", e),
542 })
543 })?;
544
545 let mut stats = self.stats.write().await;
546 stats.delete_count += 1;
547 drop(stats);
548
549 self.maybe_flush().await?;
550
551 Ok(())
552 }
553
554 async fn list_keys(&self, prefix: &[u8]) -> StateResult<Vec<Vec<u8>>> {
555 trace!("Listing keys with prefix: {:?}", prefix);
556
557 let keys = tokio::task::block_in_place(|| {
558 if prefix.is_empty() {
559 self.db
561 .iter()
562 .keys()
563 .map(|result| {
564 result
565 .map(|k| k.to_vec())
566 .map_err(|e| StateError::StorageError {
567 backend_type: "sled".to_string(),
568 details: format!("Key iteration failed: {}", e),
569 })
570 })
571 .collect::<StateResult<Vec<_>>>()
572 } else {
573 self.db
575 .scan_prefix(prefix)
576 .keys()
577 .map(|result| {
578 result
579 .map(|k| k.to_vec())
580 .map_err(|e| StateError::StorageError {
581 backend_type: "sled".to_string(),
582 details: format!("Prefix scan failed: {}", e),
583 })
584 })
585 .collect::<StateResult<Vec<_>>>()
586 }
587 })?;
588
589 Ok(keys)
590 }
591
592 async fn clear(&self) -> StateResult<()> {
593 debug!("Clearing Sled database");
594
595 tokio::task::block_in_place(|| {
596 self.db.clear().map_err(|e| StateError::StorageError {
597 backend_type: "sled".to_string(),
598 details: format!("Clear failed: {}", e),
599 })
600 })?;
601
602 self.flush().await?;
603
604 Ok(())
605 }
606
607 async fn count(&self) -> StateResult<usize> {
608 Ok(self.db.len())
609 }
610
611 async fn contains(&self, key: &[u8]) -> StateResult<bool> {
612 Ok(tokio::task::block_in_place(|| {
613 self.db.contains_key(key).unwrap_or(false)
614 }))
615 }
616}
617
618impl Drop for SledStateBackend {
620 fn drop(&mut self) {
621 if let Err(e) = self.db.flush() {
622 error!("Failed to flush Sled database on drop: {}", e);
623 }
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630 use crate::state::backend::tests::*;
631
632 async fn create_test_backend() -> SledStateBackend {
633 SledStateBackend::temporary().await.unwrap()
634 }
635
636 #[tokio::test]
637 async fn test_sled_backend_basic() {
638 let backend = create_test_backend().await;
639 test_backend_basic_ops(backend).await;
640 }
641
642 #[tokio::test]
643 async fn test_sled_backend_list_keys() {
644 let backend = create_test_backend().await;
645 test_backend_list_keys(backend).await;
646 }
647
648 #[tokio::test]
649 async fn test_sled_backend_clear() {
650 let backend = create_test_backend().await;
651 test_backend_clear(backend).await;
652 }
653
654 #[tokio::test]
655 async fn test_sled_backend_contains() {
656 let backend = create_test_backend().await;
657 test_backend_contains(backend).await;
658 }
659
660 #[tokio::test]
661 async fn test_sled_persistence() {
662 let temp_dir = std::env::temp_dir().join(format!("sled_persist_test_{}", uuid::Uuid::new_v4()));
663 let config = SledConfig::new(&temp_dir);
664
665 {
667 let backend = SledStateBackend::open(config.clone()).await.unwrap();
668 backend.put(b"key1", b"value1").await.unwrap();
669 backend.put(b"key2", b"value2").await.unwrap();
670 backend.flush().await.unwrap();
671 }
673
674 {
676 let backend = SledStateBackend::open(config).await.unwrap();
677 assert_eq!(backend.count().await.unwrap(), 2);
678 assert_eq!(
679 backend.get(b"key1").await.unwrap(),
680 Some(b"value1".to_vec())
681 );
682 assert_eq!(
683 backend.get(b"key2").await.unwrap(),
684 Some(b"value2".to_vec())
685 );
686 }
687
688 tokio::fs::remove_dir_all(&temp_dir).await.ok();
690 }
691
692 #[tokio::test]
693 async fn test_checkpoint_restore() {
694 let backend = create_test_backend().await;
695
696 backend.put(b"key1", b"value1").await.unwrap();
697 backend.put(b"key2", b"value2").await.unwrap();
698
699 let checkpoint = backend.checkpoint().await.unwrap();
701 assert_eq!(checkpoint.len(), 2);
702
703 backend.clear().await.unwrap();
705 assert_eq!(backend.count().await.unwrap(), 0);
706
707 backend.restore(checkpoint).await.unwrap();
708 assert_eq!(backend.count().await.unwrap(), 2);
709
710 assert_eq!(
711 backend.get(b"key1").await.unwrap(),
712 Some(b"value1".to_vec())
713 );
714 }
715
716 #[tokio::test]
717 async fn test_stats() {
718 let backend = create_test_backend().await;
719
720 backend.put(b"key1", b"value1").await.unwrap();
721 backend.put(b"key2", b"value2").await.unwrap();
722
723 backend.get(b"key1").await.unwrap();
724 backend.delete(b"key1").await.unwrap();
725
726 let stats = backend.stats().await;
727 assert_eq!(stats.put_count, 2);
728 assert_eq!(stats.get_count, 1);
729 assert_eq!(stats.delete_count, 1);
730 assert!(stats.bytes_written > 0);
731 assert!(stats.bytes_read > 0);
732 }
733
734 #[tokio::test]
735 async fn test_size_on_disk() {
736 let backend = create_test_backend().await;
737
738 let initial_size = backend.size_on_disk().await.unwrap();
739
740 for i in 0..100 {
742 let key = format!("key:{}", i).into_bytes();
743 let value = vec![0u8; 1000]; backend.put(&key, &value).await.unwrap();
745 }
746
747 backend.flush().await.unwrap();
748
749 let final_size = backend.size_on_disk().await.unwrap();
750 assert!(final_size > initial_size);
751 }
752
753 #[tokio::test]
754 async fn test_concurrent_access() {
755 use std::sync::Arc;
756
757 let backend = Arc::new(create_test_backend().await);
758 let mut handles = vec![];
759
760 for i in 0..10 {
762 let backend = Arc::clone(&backend);
763 let handle = tokio::spawn(async move {
764 for j in 0..10 {
765 let key = format!("key:{}:{}", i, j).into_bytes();
766 let value = format!("value:{}:{}", i, j).into_bytes();
767 backend.put(&key, &value).await.unwrap();
768 }
769 });
770 handles.push(handle);
771 }
772
773 for handle in handles {
775 handle.await.unwrap();
776 }
777
778 backend.flush().await.unwrap();
779
780 assert_eq!(backend.count().await.unwrap(), 100);
782 }
783
784 #[tokio::test]
789 async fn test_compression_roundtrip() {
790 let backend = create_test_backend().await;
791
792 let test_data = b"This is some test data that should be compressed";
793
794 backend.put(b"test_key", test_data).await.unwrap();
796 let result = backend.get(b"test_key").await.unwrap();
797
798 assert_eq!(result, Some(test_data.to_vec()));
799 }
800
801 #[tokio::test]
802 async fn test_compression_algorithms() {
803 let test_data = vec![b'x'; 10000]; {
807 let temp_dir = std::env::temp_dir().join(format!("sled_lz4_test_{}", uuid::Uuid::new_v4()));
808 let config = SledConfig::new(&temp_dir).with_fast_compression();
809 let backend = SledStateBackend::open(config).await.unwrap();
810
811 backend.put(b"key", &test_data).await.unwrap();
812 let result = backend.get(b"key").await.unwrap().unwrap();
813 assert_eq!(result, test_data);
814
815 tokio::fs::remove_dir_all(&temp_dir).await.ok();
816 }
817
818 {
820 let temp_dir = std::env::temp_dir().join(format!("sled_snappy_test_{}", uuid::Uuid::new_v4()));
821 let config = SledConfig::new(&temp_dir).with_balanced_compression();
822 let backend = SledStateBackend::open(config).await.unwrap();
823
824 backend.put(b"key", &test_data).await.unwrap();
825 let result = backend.get(b"key").await.unwrap().unwrap();
826 assert_eq!(result, test_data);
827
828 tokio::fs::remove_dir_all(&temp_dir).await.ok();
829 }
830
831 {
833 let temp_dir = std::env::temp_dir().join(format!("sled_zstd_test_{}", uuid::Uuid::new_v4()));
834 let config = SledConfig::new(&temp_dir).with_best_compression();
835 let backend = SledStateBackend::open(config).await.unwrap();
836
837 backend.put(b"key", &test_data).await.unwrap();
838 let result = backend.get(b"key").await.unwrap().unwrap();
839 assert_eq!(result, test_data);
840
841 tokio::fs::remove_dir_all(&temp_dir).await.ok();
842 }
843
844 {
846 let temp_dir = std::env::temp_dir().join(format!("sled_none_test_{}", uuid::Uuid::new_v4()));
847 let config = SledConfig::new(&temp_dir).without_compression();
848 let backend = SledStateBackend::open(config).await.unwrap();
849
850 backend.put(b"key", &test_data).await.unwrap();
851 let result = backend.get(b"key").await.unwrap().unwrap();
852 assert_eq!(result, test_data);
853
854 tokio::fs::remove_dir_all(&temp_dir).await.ok();
855 }
856 }
857
858 #[tokio::test]
859 async fn test_compression_stats() {
860 let backend = create_test_backend().await;
861
862 let data = vec![b'A'; 10000]; backend.put(b"key", &data).await.unwrap();
865
866 let stats = backend.stats().await;
867 assert!(stats.bytes_compressed > 0);
868 assert!(stats.bytes_before_compression >= 10000);
869 assert!(stats.bytes_after_compression > 0);
870
871 assert!(stats.bytes_after_compression < stats.bytes_before_compression);
873 }
874
875 #[tokio::test]
876 async fn test_compression_ratio() {
877 let backend = create_test_backend().await;
878
879 let data = vec![0u8; 100000]; backend.put(b"key", &data).await.unwrap();
882
883 let ratio = backend.compression_ratio().await;
884 assert!(ratio.is_some());
885
886 let ratio_value = ratio.unwrap();
887 assert!(ratio_value > 0.0);
888 assert!(ratio_value < 100.0); println!("Compression ratio: {:.2}%", ratio_value);
891 }
892
893 #[tokio::test]
894 async fn test_space_saved() {
895 let backend = create_test_backend().await;
896
897 let data = vec![b'X'; 50000]; backend.put(b"key", &data).await.unwrap();
900
901 let space_saved = backend.space_saved().await;
902 assert!(space_saved > 0);
903
904 println!("Space saved: {} bytes", space_saved);
905 }
906
907 #[tokio::test]
908 async fn test_large_data_compression() {
909 let backend = create_test_backend().await;
910
911 let large_data = vec![b'Z'; 1024 * 1024];
913
914 backend.put(b"large_key", &large_data).await.unwrap();
915 let result = backend.get(b"large_key").await.unwrap().unwrap();
916
917 assert_eq!(result.len(), large_data.len());
918 assert_eq!(result, large_data);
919
920 let stats = backend.stats().await;
922 let compression_ratio = (stats.bytes_after_compression as f64
923 / stats.bytes_before_compression as f64) * 100.0;
924
925 println!("Large data compression ratio: {:.2}%", compression_ratio);
926
927 assert!(compression_ratio < 10.0, "Compression ratio should be less than 10% for repeated data");
929 }
930
931 #[tokio::test]
932 async fn test_mixed_data_compression() {
933 let backend = create_test_backend().await;
934
935 let compressible = vec![b'A'; 5000];
937 let random = (0..5000).map(|i| (i % 256) as u8).collect::<Vec<_>>();
938
939 backend.put(b"compressible", &compressible).await.unwrap();
940 backend.put(b"random", &random).await.unwrap();
941
942 assert_eq!(
944 backend.get(b"compressible").await.unwrap().unwrap(),
945 compressible
946 );
947 assert_eq!(
948 backend.get(b"random").await.unwrap().unwrap(),
949 random
950 );
951
952 let stats = backend.stats().await;
953 assert!(stats.bytes_compressed > 0);
954 assert!(stats.bytes_decompressed > 0);
955 }
956
957 #[tokio::test]
958 async fn test_compression_persistence() {
959 let temp_dir = std::env::temp_dir().join(format!("sled_compress_persist_{}", uuid::Uuid::new_v4()));
960 let config = SledConfig::new(&temp_dir).with_best_compression();
961
962 let test_data = vec![b'B'; 20000];
963
964 {
966 let backend = SledStateBackend::open(config.clone()).await.unwrap();
967 backend.put(b"key", &test_data).await.unwrap();
968 backend.flush().await.unwrap();
969 }
970
971 {
973 let backend = SledStateBackend::open(config).await.unwrap();
974 let result = backend.get(b"key").await.unwrap().unwrap();
975 assert_eq!(result, test_data);
976 }
977
978 tokio::fs::remove_dir_all(&temp_dir).await.ok();
980 }
981
982 #[tokio::test]
983 async fn test_compression_with_custom_config() {
984 use super::compression::{CompressionAlgorithm, CompressionConfig};
985
986 let temp_dir = std::env::temp_dir().join(format!("sled_custom_compress_{}", uuid::Uuid::new_v4()));
987
988 let compression_config = CompressionConfig {
990 algorithm: CompressionAlgorithm::Zstd,
991 level: 15,
992 threshold: 512,
993 enable_stats: true,
994 };
995
996 let config = SledConfig::new(&temp_dir).with_compression(compression_config);
997 let backend = SledStateBackend::open(config).await.unwrap();
998
999 let small_data = vec![b'S'; 256];
1001 backend.put(b"small", &small_data).await.unwrap();
1002
1003 let large_data = vec![b'L'; 10000];
1005 backend.put(b"large", &large_data).await.unwrap();
1006
1007 assert_eq!(
1009 backend.get(b"small").await.unwrap().unwrap(),
1010 small_data
1011 );
1012 assert_eq!(
1013 backend.get(b"large").await.unwrap().unwrap(),
1014 large_data
1015 );
1016
1017 tokio::fs::remove_dir_all(&temp_dir).await.ok();
1019 }
1020
1021 #[tokio::test]
1022 async fn test_empty_value_compression() {
1023 let backend = create_test_backend().await;
1024
1025 backend.put(b"empty", b"").await.unwrap();
1027 let result = backend.get(b"empty").await.unwrap();
1028 assert_eq!(result, Some(vec![]));
1029 }
1030
1031 #[tokio::test]
1032 async fn test_compression_stats_accuracy() {
1033 let backend = create_test_backend().await;
1034
1035 let data1 = vec![b'1'; 5000];
1036 let data2 = vec![b'2'; 8000];
1037 let data3 = vec![b'3'; 12000];
1038
1039 backend.put(b"key1", &data1).await.unwrap();
1040 backend.put(b"key2", &data2).await.unwrap();
1041 backend.put(b"key3", &data3).await.unwrap();
1042
1043 let stats = backend.stats().await;
1044
1045 assert!(stats.bytes_before_compression >= 25000);
1047
1048 assert!(stats.bytes_after_compression < stats.bytes_before_compression);
1050
1051 backend.get(b"key1").await.unwrap();
1053 backend.get(b"key2").await.unwrap();
1054 backend.get(b"key3").await.unwrap();
1055
1056 let stats = backend.stats().await;
1057 assert!(stats.bytes_decompressed >= 25000);
1058 }
1059}