1use crate::compression::{
8 AdvancedCompressionType, CompressedData, CompressionAlgorithm, CompressionMetadata,
9};
10use anyhow::{anyhow, Result};
11use scirs2_core::metrics::{Counter, Gauge, Histogram};
12use std::collections::HashMap;
13use std::time::Instant;
14
15pub struct ColumnStoreCompressor {
17 columns: Vec<ColumnDefinition>,
19 stats: ColumnStoreStats,
21 analytics_mode: bool,
23}
24
25#[derive(Debug, Clone, Default)]
27pub struct ColumnStoreStats {
28 pub rows_compressed: u64,
30 pub bytes_compressed: u64,
32 pub compression_time_us: u64,
34 pub decompression_time_us: u64,
36 pub avg_compression_ratio: f64,
38 pub column_stats: HashMap<String, ColumnStats>,
40}
41
42#[derive(Debug, Clone, Default)]
44pub struct ColumnStats {
45 pub value_count: u64,
47 pub unique_values: u64,
49 pub null_count: u64,
51 pub min_value: Option<f64>,
53 pub max_value: Option<f64>,
55 pub avg_value_length: f64,
57 pub compression_ratio: f64,
59}
60
61#[derive(Debug, Clone)]
63pub struct ColumnDefinition {
64 pub name: String,
66 pub data_type: ColumnDataType,
68 pub compression: ColumnCompressionType,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum ColumnDataType {
75 Int8,
77 Int16,
79 Int32,
81 Int64,
83 Float32,
85 Float64,
87 String,
89 Binary,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ColumnCompressionType {
96 None,
98 RunLength,
100 Delta,
102 Dictionary,
104 FrameOfReference,
106 Bitmap,
108 Lz4,
110}
111
112#[derive(Debug, Clone)]
114pub struct RdfTripleLayout {
115 pub subject: ColumnDefinition,
117 pub predicate: ColumnDefinition,
119 pub object: ColumnDefinition,
121}
122
123impl RdfTripleLayout {
124 pub fn new() -> Self {
126 Self {
127 subject: ColumnDefinition {
128 name: "subject".to_string(),
129 data_type: ColumnDataType::Int64, compression: ColumnCompressionType::Delta,
131 },
132 predicate: ColumnDefinition {
133 name: "predicate".to_string(),
134 data_type: ColumnDataType::Int64, compression: ColumnCompressionType::Dictionary, },
137 object: ColumnDefinition {
138 name: "object".to_string(),
139 data_type: ColumnDataType::Int64, compression: ColumnCompressionType::Delta,
141 },
142 }
143 }
144
145 pub fn columns(&self) -> Vec<ColumnDefinition> {
147 vec![
148 self.subject.clone(),
149 self.predicate.clone(),
150 self.object.clone(),
151 ]
152 }
153}
154
155impl Default for RdfTripleLayout {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161#[derive(Debug, Clone)]
163pub struct BatchCompressionConfig {
164 pub batch_size: usize,
166 pub parallel: bool,
168 pub target_ratio: f64,
170}
171
172impl ColumnStoreCompressor {
173 pub fn new() -> Self {
175 Self {
176 columns: Vec::new(),
177 stats: ColumnStoreStats::default(),
178 analytics_mode: false,
179 }
180 }
181
182 pub fn with_analytics() -> Self {
184 Self {
185 columns: Vec::new(),
186 stats: ColumnStoreStats::default(),
187 analytics_mode: true,
188 }
189 }
190
191 pub fn for_rdf_triples() -> Self {
193 let layout = RdfTripleLayout::new();
194 let mut compressor = Self::with_analytics();
195 for column in layout.columns() {
196 compressor.add_column(column);
197 }
198 compressor
199 }
200
201 pub fn add_column(&mut self, column: ColumnDefinition) {
203 self.columns.push(column);
204 }
205
206 pub fn get_stats(&self) -> &ColumnStoreStats {
208 &self.stats
209 }
210
211 pub fn reset_stats(&mut self) {
213 self.stats = ColumnStoreStats::default();
214 }
215
216 pub fn analyze_column(&mut self, column_name: &str, values: &[Vec<u8>]) {
218 if values.is_empty() {
219 return;
220 }
221
222 let mut unique_values = std::collections::HashSet::new();
224 let mut total_size = 0;
225 let mut is_sequential = true;
226 let mut last_value: Option<u64> = None;
227
228 for value in values {
229 unique_values.insert(value.clone());
230 total_size += value.len();
231
232 if value.len() >= 8 {
234 let current_value = u64::from_le_bytes([
235 value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
236 ]);
237
238 if let Some(last) = last_value {
239 if current_value != last + 1 {
240 is_sequential = false;
241 }
242 }
243 last_value = Some(current_value);
244 } else {
245 is_sequential = false;
246 }
247 }
248
249 let uniqueness_ratio = unique_values.len() as f64 / values.len() as f64;
250 let avg_size = total_size / values.len();
251
252 let compression_type = if is_sequential {
254 ColumnCompressionType::Delta
255 } else if uniqueness_ratio < 0.1 {
256 ColumnCompressionType::RunLength
258 } else if avg_size > 8 && uniqueness_ratio < 0.5 {
259 ColumnCompressionType::Dictionary
261 } else {
262 ColumnCompressionType::None
264 };
265
266 if let Some(column) = self.columns.iter_mut().find(|c| c.name == column_name) {
268 column.compression = compression_type;
269 } else {
270 let data_type = if avg_size <= 1 {
272 ColumnDataType::Int8
273 } else if avg_size <= 2 {
274 ColumnDataType::Int16
275 } else if avg_size <= 4 {
276 ColumnDataType::Int32
277 } else if avg_size <= 8 {
278 ColumnDataType::Int64
279 } else {
280 ColumnDataType::Binary
281 };
282
283 self.add_column(ColumnDefinition {
284 name: column_name.to_string(),
285 data_type,
286 compression: compression_type,
287 });
288 }
289 }
290
291 pub fn compress_batch(
293 &mut self,
294 rows: &[Vec<u8>],
295 config: &BatchCompressionConfig,
296 ) -> Result<Vec<u8>> {
297 if rows.is_empty() {
298 return Ok(Vec::new());
299 }
300
301 let start = Instant::now();
302 let mut all_compressed = Vec::new();
303
304 for chunk in rows.chunks(config.batch_size) {
306 let compressed_chunk = self.compress_rows(chunk)?;
307 all_compressed.extend_from_slice(&(compressed_chunk.len() as u32).to_le_bytes());
308 all_compressed.extend_from_slice(&compressed_chunk);
309 }
310
311 self.stats.rows_compressed += rows.len() as u64;
313 self.stats.bytes_compressed += rows.iter().map(|r| r.len()).sum::<usize>() as u64;
314 self.stats.compression_time_us += start.elapsed().as_micros() as u64;
315
316 let original_size: usize = rows.iter().map(|r| r.len()).sum();
317 let compressed_size = all_compressed.len();
318 if original_size > 0 {
319 let ratio = compressed_size as f64 / original_size as f64;
320 let total_compressions = self.stats.rows_compressed / config.batch_size as u64;
322 if total_compressions > 0 {
323 self.stats.avg_compression_ratio =
324 (self.stats.avg_compression_ratio * (total_compressions - 1) as f64 + ratio)
325 / total_compressions as f64;
326 } else {
327 self.stats.avg_compression_ratio = ratio;
328 }
329 }
330
331 Ok(all_compressed)
332 }
333
334 pub fn decompress_batch(&mut self, compressed: &[u8]) -> Result<Vec<Vec<u8>>> {
336 let start = Instant::now();
337 let mut all_rows = Vec::new();
338 let mut offset = 0;
339
340 while offset < compressed.len() {
341 if offset + 4 > compressed.len() {
342 break;
343 }
344
345 let chunk_size = u32::from_le_bytes([
346 compressed[offset],
347 compressed[offset + 1],
348 compressed[offset + 2],
349 compressed[offset + 3],
350 ]) as usize;
351 offset += 4;
352
353 if offset + chunk_size > compressed.len() {
354 return Err(anyhow!("Truncated batch data"));
355 }
356
357 let chunk = &compressed[offset..offset + chunk_size];
358 let rows = self.decompress_to_rows(chunk)?;
359 all_rows.extend(rows);
360 offset += chunk_size;
361 }
362
363 self.stats.decompression_time_us += start.elapsed().as_micros() as u64;
364
365 Ok(all_rows)
366 }
367
368 pub fn collect_column_stats(&mut self, column_name: &str, values: &[Vec<u8>]) {
370 if values.is_empty() {
371 return;
372 }
373
374 let mut stats = ColumnStats {
375 value_count: values.len() as u64,
376 ..Default::default()
377 };
378
379 let mut unique_values = std::collections::HashSet::new();
380 let mut total_length = 0;
381 let mut min_val = f64::MAX;
382 let mut max_val = f64::MIN;
383
384 for value in values {
385 unique_values.insert(value.clone());
386 total_length += value.len();
387
388 if value.len() == 8 {
390 let num_val = f64::from_le_bytes([
391 value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
392 ]);
393 if num_val.is_finite() {
394 min_val = min_val.min(num_val);
395 max_val = max_val.max(num_val);
396 }
397 }
398 }
399
400 stats.unique_values = unique_values.len() as u64;
401 stats.avg_value_length = total_length as f64 / values.len() as f64;
402
403 if min_val != f64::MAX && max_val != f64::MIN {
404 stats.min_value = Some(min_val);
405 stats.max_value = Some(max_val);
406 }
407
408 self.stats
409 .column_stats
410 .insert(column_name.to_string(), stats);
411 }
412
413 pub fn get_optimal_compression(&self, column_name: &str) -> ColumnCompressionType {
415 if let Some(stats) = self.stats.column_stats.get(column_name) {
416 let uniqueness_ratio = stats.unique_values as f64 / stats.value_count as f64;
418
419 if uniqueness_ratio < 0.01 {
420 ColumnCompressionType::Bitmap
422 } else if uniqueness_ratio < 0.1 {
423 ColumnCompressionType::Dictionary
425 } else if let (Some(min), Some(max)) = (stats.min_value, stats.max_value) {
426 let range = max - min;
428 if range < 1000.0 {
429 ColumnCompressionType::FrameOfReference
430 } else {
431 ColumnCompressionType::Delta
432 }
433 } else {
434 ColumnCompressionType::Lz4
436 }
437 } else {
438 ColumnCompressionType::None
439 }
440 }
441
442 pub fn compress_rows(&self, rows: &[Vec<u8>]) -> Result<Vec<u8>> {
444 if rows.is_empty() {
445 return Ok(Vec::new());
446 }
447
448 if self.columns.is_empty() {
449 return Err(anyhow!("No column definitions provided"));
450 }
451
452 let row_count = rows.len();
453 let mut compressed = Vec::new();
454
455 compressed.extend_from_slice(&(row_count as u32).to_le_bytes());
457 compressed.extend_from_slice(&(self.columns.len() as u32).to_le_bytes());
458
459 for (col_idx, column) in self.columns.iter().enumerate() {
461 let mut column_data = Vec::new();
462
463 for row in rows {
465 let cell_data = self.extract_cell_data(row, col_idx, &column.data_type)?;
466 column_data.extend_from_slice(&cell_data);
467 }
468
469 let compressed_column = self.compress_column_data(&column_data, column.compression)?;
471
472 compressed.extend_from_slice(&(compressed_column.len() as u32).to_le_bytes());
474 compressed.extend_from_slice(&compressed_column);
475 }
476
477 Ok(compressed)
478 }
479
480 pub fn decompress_to_rows(&self, compressed: &[u8]) -> Result<Vec<Vec<u8>>> {
482 if compressed.is_empty() {
483 return Ok(Vec::new());
484 }
485
486 if compressed.len() < 8 {
487 return Err(anyhow!("Invalid compressed data"));
488 }
489
490 let row_count =
491 u32::from_le_bytes([compressed[0], compressed[1], compressed[2], compressed[3]])
492 as usize;
493
494 let col_count =
495 u32::from_le_bytes([compressed[4], compressed[5], compressed[6], compressed[7]])
496 as usize;
497
498 if col_count != self.columns.len() {
499 return Err(anyhow!("Column count mismatch"));
500 }
501
502 let mut offset = 8;
503 let mut column_data = Vec::new();
504
505 for column in &self.columns {
507 if offset + 4 > compressed.len() {
508 return Err(anyhow!("Truncated column data"));
509 }
510
511 let column_size = u32::from_le_bytes([
512 compressed[offset],
513 compressed[offset + 1],
514 compressed[offset + 2],
515 compressed[offset + 3],
516 ]) as usize;
517 offset += 4;
518
519 if offset + column_size > compressed.len() {
520 return Err(anyhow!("Truncated column data"));
521 }
522
523 let compressed_column = &compressed[offset..offset + column_size];
524 let decompressed_column =
525 self.decompress_column_data(compressed_column, column.compression)?;
526 column_data.push(decompressed_column);
527 offset += column_size;
528 }
529
530 let mut rows = Vec::new();
532 for row_idx in 0..row_count {
533 let mut row = Vec::new();
534 for (col_idx, column) in self.columns.iter().enumerate() {
535 let cell_data =
536 self.extract_row_cell(&column_data[col_idx], row_idx, &column.data_type)?;
537 row.extend_from_slice(&cell_data);
538 }
539 rows.push(row);
540 }
541
542 Ok(rows)
543 }
544
545 fn extract_cell_data(
547 &self,
548 row: &[u8],
549 col_idx: usize,
550 data_type: &ColumnDataType,
551 ) -> Result<Vec<u8>> {
552 let cell_size = match data_type {
553 ColumnDataType::Int8 => 1,
554 ColumnDataType::Int16 => 2,
555 ColumnDataType::Int32 | ColumnDataType::Float32 => 4,
556 ColumnDataType::Int64 | ColumnDataType::Float64 => 8,
557 ColumnDataType::String | ColumnDataType::Binary => {
558 8
560 }
561 };
562
563 let start = col_idx * cell_size;
564 if start + cell_size > row.len() {
565 return Err(anyhow!("Row data too short for column {}", col_idx));
566 }
567
568 Ok(row[start..start + cell_size].to_vec())
569 }
570
571 fn extract_row_cell(
573 &self,
574 column_data: &[u8],
575 row_idx: usize,
576 data_type: &ColumnDataType,
577 ) -> Result<Vec<u8>> {
578 let cell_size = match data_type {
579 ColumnDataType::Int8 => 1,
580 ColumnDataType::Int16 => 2,
581 ColumnDataType::Int32 | ColumnDataType::Float32 => 4,
582 ColumnDataType::Int64 | ColumnDataType::Float64 => 8,
583 ColumnDataType::String | ColumnDataType::Binary => 8,
584 };
585
586 let start = row_idx * cell_size;
587 if start + cell_size > column_data.len() {
588 return Err(anyhow!("Column data too short for row {}", row_idx));
589 }
590
591 Ok(column_data[start..start + cell_size].to_vec())
592 }
593
594 fn compress_column_data(
596 &self,
597 data: &[u8],
598 compression: ColumnCompressionType,
599 ) -> Result<Vec<u8>> {
600 match compression {
601 ColumnCompressionType::None => Ok(data.to_vec()),
602 ColumnCompressionType::RunLength => {
603 crate::compression::run_length::RunLengthEncoder::encode(data)
604 }
605 ColumnCompressionType::Delta => {
606 crate::compression::delta::DeltaEncoder::encode_byte_sequence(data)
607 }
608 _ => {
609 Ok(data.to_vec())
611 }
612 }
613 }
614
615 fn decompress_column_data(
617 &self,
618 compressed: &[u8],
619 compression: ColumnCompressionType,
620 ) -> Result<Vec<u8>> {
621 match compression {
622 ColumnCompressionType::None => Ok(compressed.to_vec()),
623 ColumnCompressionType::RunLength => {
624 crate::compression::run_length::RunLengthEncoder::decode(compressed)
625 }
626 ColumnCompressionType::Delta => {
627 crate::compression::delta::DeltaEncoder::decode_byte_sequence(compressed)
628 }
629 _ => {
630 Ok(compressed.to_vec())
632 }
633 }
634 }
635}
636
637impl Default for ColumnStoreCompressor {
638 fn default() -> Self {
639 Self::new()
640 }
641}
642
643impl CompressionAlgorithm for ColumnStoreCompressor {
644 fn compress(&self, data: &[u8]) -> Result<CompressedData> {
645 if self.columns.is_empty() {
646 return Err(anyhow!(
647 "No column definitions for column store compression"
648 ));
649 }
650
651 let rows = vec![data.to_vec()];
653
654 let start = Instant::now();
655 let compressed_bytes = self.compress_rows(&rows)?;
656 let compression_time = start.elapsed();
657
658 let mut metadata_map = HashMap::new();
659 metadata_map.insert("columns".to_string(), self.columns.len().to_string());
660 metadata_map.insert("rows".to_string(), "1".to_string());
661
662 let metadata = CompressionMetadata {
663 algorithm: AdvancedCompressionType::ColumnStore,
664 original_size: data.len() as u64,
665 compressed_size: compressed_bytes.len() as u64,
666 compression_time_us: compression_time.as_micros() as u64,
667 metadata: metadata_map,
668 };
669
670 Ok(CompressedData {
671 data: compressed_bytes,
672 metadata,
673 })
674 }
675
676 fn decompress(&self, compressed: &CompressedData) -> Result<Vec<u8>> {
677 if compressed.metadata.algorithm != AdvancedCompressionType::ColumnStore {
678 return Err(anyhow!(
679 "Invalid compression algorithm: expected ColumnStore, got {}",
680 compressed.metadata.algorithm
681 ));
682 }
683
684 let rows = self.decompress_to_rows(&compressed.data)?;
685 if rows.is_empty() {
686 Ok(Vec::new())
687 } else {
688 Ok(rows[0].clone())
689 }
690 }
691
692 fn algorithm_type(&self) -> AdvancedCompressionType {
693 AdvancedCompressionType::ColumnStore
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700
701 #[test]
702 fn test_column_store_basic() {
703 let mut compressor = ColumnStoreCompressor::new();
704 compressor.add_column(ColumnDefinition {
705 name: "id".to_string(),
706 data_type: ColumnDataType::Int64,
707 compression: ColumnCompressionType::None,
708 });
709 compressor.add_column(ColumnDefinition {
710 name: "value".to_string(),
711 data_type: ColumnDataType::Int64,
712 compression: ColumnCompressionType::Delta,
713 });
714
715 let test_data = vec![0u8; 16];
717 let compressed = compressor.compress(&test_data).unwrap();
718 assert_eq!(
719 compressed.metadata.algorithm,
720 AdvancedCompressionType::ColumnStore
721 );
722
723 let decompressed = compressor.decompress(&compressed).unwrap();
724 assert_eq!(test_data, decompressed);
725 }
726
727 #[test]
728 fn test_column_definitions() {
729 let mut compressor = ColumnStoreCompressor::new();
730
731 compressor.add_column(ColumnDefinition {
732 name: "test".to_string(),
733 data_type: ColumnDataType::Int32,
734 compression: ColumnCompressionType::RunLength,
735 });
736
737 assert_eq!(compressor.columns.len(), 1);
738 assert_eq!(compressor.columns[0].name, "test");
739 }
740
741 #[test]
742 fn test_analytics_mode() {
743 let compressor = ColumnStoreCompressor::with_analytics();
744 assert!(compressor.analytics_mode);
745 assert_eq!(compressor.columns.len(), 0);
746 }
747
748 #[test]
749 fn test_rdf_triple_layout() {
750 let layout = RdfTripleLayout::new();
751 assert_eq!(layout.subject.name, "subject");
752 assert_eq!(layout.predicate.name, "predicate");
753 assert_eq!(layout.object.name, "object");
754
755 let columns = layout.columns();
756 assert_eq!(columns.len(), 3);
757 }
758
759 #[test]
760 fn test_rdf_triple_compressor() {
761 let compressor = ColumnStoreCompressor::for_rdf_triples();
762 assert_eq!(compressor.columns.len(), 3);
763 assert!(compressor.analytics_mode);
764 assert_eq!(compressor.columns[0].name, "subject");
765 assert_eq!(compressor.columns[1].name, "predicate");
766 assert_eq!(compressor.columns[2].name, "object");
767 }
768
769 #[test]
770 fn test_batch_compression() {
771 let mut compressor = ColumnStoreCompressor::new();
772 compressor.add_column(ColumnDefinition {
773 name: "col1".to_string(),
774 data_type: ColumnDataType::Int64,
775 compression: ColumnCompressionType::None,
776 });
777
778 let config = BatchCompressionConfig {
779 batch_size: 10,
780 parallel: false,
781 target_ratio: 0.5,
782 };
783
784 let rows: Vec<Vec<u8>> = (0..20).map(|_| vec![0u8; 8]).collect();
786
787 let compressed = compressor.compress_batch(&rows, &config).unwrap();
788 assert!(!compressed.is_empty());
789
790 let decompressed = compressor.decompress_batch(&compressed).unwrap();
791 assert_eq!(decompressed.len(), 20);
792 }
793
794 #[test]
795 fn test_column_statistics() {
796 let mut compressor = ColumnStoreCompressor::new();
797
798 let values: Vec<Vec<u8>> = vec![
799 vec![1, 0, 0, 0, 0, 0, 0, 0],
800 vec![2, 0, 0, 0, 0, 0, 0, 0],
801 vec![1, 0, 0, 0, 0, 0, 0, 0], vec![3, 0, 0, 0, 0, 0, 0, 0],
803 ];
804
805 compressor.collect_column_stats("test_col", &values);
806
807 let stats = compressor.get_stats();
808 assert!(stats.column_stats.contains_key("test_col"));
809
810 let col_stats = &stats.column_stats["test_col"];
811 assert_eq!(col_stats.value_count, 4);
812 assert_eq!(col_stats.unique_values, 3); assert_eq!(col_stats.avg_value_length, 8.0);
814 }
815
816 #[test]
817 fn test_optimal_compression_selection() {
818 let mut compressor = ColumnStoreCompressor::new();
819
820 let low_card_values: Vec<Vec<u8>> = (0..100)
822 .map(|i| {
823 let val = (i % 5) as u64; val.to_le_bytes().to_vec()
825 })
826 .collect();
827
828 compressor.collect_column_stats("low_card", &low_card_values);
829 let compression = compressor.get_optimal_compression("low_card");
830 assert!(matches!(
832 compression,
833 ColumnCompressionType::Dictionary | ColumnCompressionType::Bitmap
834 ));
835
836 let high_card_values: Vec<Vec<u8>> = (0..100)
838 .map(|i| {
839 let val = i as u64;
840 val.to_le_bytes().to_vec()
841 })
842 .collect();
843
844 compressor.collect_column_stats("high_card", &high_card_values);
845 let compression = compressor.get_optimal_compression("high_card");
846 assert!(matches!(
848 compression,
849 ColumnCompressionType::Delta | ColumnCompressionType::FrameOfReference
850 ));
851 }
852
853 #[test]
854 fn test_compression_stats_tracking() {
855 let mut compressor = ColumnStoreCompressor::new();
856 compressor.add_column(ColumnDefinition {
857 name: "col1".to_string(),
858 data_type: ColumnDataType::Int64,
859 compression: ColumnCompressionType::None,
860 });
861
862 let config = BatchCompressionConfig {
863 batch_size: 10,
864 parallel: false,
865 target_ratio: 0.5,
866 };
867
868 let rows: Vec<Vec<u8>> = (0..10).map(|_| vec![0u8; 8]).collect();
869 compressor.compress_batch(&rows, &config).unwrap();
870
871 let stats = compressor.get_stats();
872 assert_eq!(stats.rows_compressed, 10);
873 assert!(stats.compression_time_us > 0);
874 }
875
876 #[test]
877 fn test_reset_stats() {
878 let mut compressor = ColumnStoreCompressor::new();
879 compressor.add_column(ColumnDefinition {
880 name: "col1".to_string(),
881 data_type: ColumnDataType::Int64,
882 compression: ColumnCompressionType::None,
883 });
884
885 let config = BatchCompressionConfig {
886 batch_size: 5,
887 parallel: false,
888 target_ratio: 0.5,
889 };
890
891 let rows: Vec<Vec<u8>> = (0..5).map(|_| vec![0u8; 8]).collect();
892 compressor.compress_batch(&rows, &config).unwrap();
893
894 assert!(compressor.get_stats().rows_compressed > 0);
895
896 compressor.reset_stats();
897 assert_eq!(compressor.get_stats().rows_compressed, 0);
898 assert_eq!(compressor.get_stats().compression_time_us, 0);
899 }
900
901 #[test]
902 fn test_batch_decompress_empty() {
903 let mut compressor = ColumnStoreCompressor::new();
904 let result = compressor.decompress_batch(&[]).unwrap();
905 assert!(result.is_empty());
906 }
907
908 #[test]
909 fn test_numeric_range_detection() {
910 let mut compressor = ColumnStoreCompressor::new();
911
912 let small_range: Vec<Vec<u8>> = (100..200)
914 .map(|i| {
915 let val = i as f64;
916 val.to_le_bytes().to_vec()
917 })
918 .collect();
919
920 compressor.collect_column_stats("small_range", &small_range);
921 let stats = &compressor.get_stats().column_stats["small_range"];
922
923 assert!(stats.min_value.is_some());
924 assert!(stats.max_value.is_some());
925 assert!((stats.min_value.unwrap() - 100.0).abs() < 0.01);
926 assert!((stats.max_value.unwrap() - 199.0).abs() < 0.01);
927
928 let compression = compressor.get_optimal_compression("small_range");
929 assert_eq!(compression, ColumnCompressionType::FrameOfReference);
931 }
932
933 #[test]
934 fn test_column_compression_types() {
935 assert_ne!(ColumnCompressionType::None, ColumnCompressionType::Delta);
937 assert_ne!(
938 ColumnCompressionType::RunLength,
939 ColumnCompressionType::Dictionary
940 );
941 assert_ne!(ColumnCompressionType::Bitmap, ColumnCompressionType::Lz4);
942 }
943
944 #[test]
945 fn test_batch_config_defaults() {
946 let config = BatchCompressionConfig {
947 batch_size: 1000,
948 parallel: true,
949 target_ratio: 0.7,
950 };
951
952 assert_eq!(config.batch_size, 1000);
953 assert!(config.parallel);
954 assert_eq!(config.target_ratio, 0.7);
955 }
956}