Skip to main content

oxirs_tdb/compression/
column_store.rs

1//! # Column-store compression implementation
2//!
3//! Provides analytics-optimized column-oriented compression for RDF triples
4//! with support for batch processing, SIMD operations, and intelligent
5//! per-column compression strategy selection.
6
7use 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
15/// Column-store compressor with per-column optimization
16pub struct ColumnStoreCompressor {
17    /// Column definitions
18    columns: Vec<ColumnDefinition>,
19    /// Compression statistics
20    stats: ColumnStoreStats,
21    /// Enable analytics optimizations
22    analytics_mode: bool,
23}
24
25/// Column store compression statistics
26#[derive(Debug, Clone, Default)]
27pub struct ColumnStoreStats {
28    /// Total rows compressed
29    pub rows_compressed: u64,
30    /// Total bytes compressed
31    pub bytes_compressed: u64,
32    /// Total compression time (microseconds)
33    pub compression_time_us: u64,
34    /// Total decompression time (microseconds)
35    pub decompression_time_us: u64,
36    /// Average compression ratio
37    pub avg_compression_ratio: f64,
38    /// Per-column statistics
39    pub column_stats: HashMap<String, ColumnStats>,
40}
41
42/// Statistics for individual column
43#[derive(Debug, Clone, Default)]
44pub struct ColumnStats {
45    /// Number of values in column
46    pub value_count: u64,
47    /// Unique value count
48    pub unique_values: u64,
49    /// Null count
50    pub null_count: u64,
51    /// Min value (for numerics)
52    pub min_value: Option<f64>,
53    /// Max value (for numerics)
54    pub max_value: Option<f64>,
55    /// Average value length (bytes)
56    pub avg_value_length: f64,
57    /// Compression ratio for this column
58    pub compression_ratio: f64,
59}
60
61/// Column definition
62#[derive(Debug, Clone)]
63pub struct ColumnDefinition {
64    /// Column name
65    pub name: String,
66    /// Column data type
67    pub data_type: ColumnDataType,
68    /// Compression strategy for this column
69    pub compression: ColumnCompressionType,
70}
71
72/// Column data types
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum ColumnDataType {
75    /// 8-bit integer
76    Int8,
77    /// 16-bit integer
78    Int16,
79    /// 32-bit integer
80    Int32,
81    /// 64-bit integer
82    Int64,
83    /// 32-bit float
84    Float32,
85    /// 64-bit float
86    Float64,
87    /// Variable-length string
88    String,
89    /// Fixed-length binary
90    Binary,
91}
92
93/// Column compression types
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ColumnCompressionType {
96    /// No compression
97    None,
98    /// Run-length encoding (good for repetitive data)
99    RunLength,
100    /// Delta encoding (good for sequential data)
101    Delta,
102    /// Dictionary encoding (good for strings)
103    Dictionary,
104    /// Frame of reference (good for clustered integers)
105    FrameOfReference,
106    /// Bitmap encoding (good for sparse boolean data)
107    Bitmap,
108    /// LZ4 for mixed data
109    Lz4,
110}
111
112/// RDF triple column layout for analytics
113#[derive(Debug, Clone)]
114pub struct RdfTripleLayout {
115    /// Subject column
116    pub subject: ColumnDefinition,
117    /// Predicate column
118    pub predicate: ColumnDefinition,
119    /// Object column
120    pub object: ColumnDefinition,
121}
122
123impl RdfTripleLayout {
124    /// Create optimized RDF triple layout
125    pub fn new() -> Self {
126        Self {
127            subject: ColumnDefinition {
128                name: "subject".to_string(),
129                data_type: ColumnDataType::Int64, // NodeId
130                compression: ColumnCompressionType::Delta,
131            },
132            predicate: ColumnDefinition {
133                name: "predicate".to_string(),
134                data_type: ColumnDataType::Int64, // NodeId
135                compression: ColumnCompressionType::Dictionary, // Usually few unique predicates
136            },
137            object: ColumnDefinition {
138                name: "object".to_string(),
139                data_type: ColumnDataType::Int64, // NodeId
140                compression: ColumnCompressionType::Delta,
141            },
142        }
143    }
144
145    /// Get all columns
146    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/// Batch compression configuration
162#[derive(Debug, Clone)]
163pub struct BatchCompressionConfig {
164    /// Batch size in rows
165    pub batch_size: usize,
166    /// Enable parallel compression
167    pub parallel: bool,
168    /// Target compression ratio
169    pub target_ratio: f64,
170}
171
172impl ColumnStoreCompressor {
173    /// Create new column store compressor
174    pub fn new() -> Self {
175        Self {
176            columns: Vec::new(),
177            stats: ColumnStoreStats::default(),
178            analytics_mode: false,
179        }
180    }
181
182    /// Create column store compressor with analytics optimizations
183    pub fn with_analytics() -> Self {
184        Self {
185            columns: Vec::new(),
186            stats: ColumnStoreStats::default(),
187            analytics_mode: true,
188        }
189    }
190
191    /// Create compressor for RDF triples
192    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    /// Add column definition
202    pub fn add_column(&mut self, column: ColumnDefinition) {
203        self.columns.push(column);
204    }
205
206    /// Get compression statistics
207    pub fn get_stats(&self) -> &ColumnStoreStats {
208        &self.stats
209    }
210
211    /// Reset statistics
212    pub fn reset_stats(&mut self) {
213        self.stats = ColumnStoreStats::default();
214    }
215
216    /// Analyze column data to determine optimal compression strategy
217    pub fn analyze_column(&mut self, column_name: &str, values: &[Vec<u8>]) {
218        if values.is_empty() {
219            return;
220        }
221
222        // Simple analysis to determine best compression type
223        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            // Check if values are sequential (for delta compression)
233            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        // Determine optimal compression strategy
253        let compression_type = if is_sequential {
254            ColumnCompressionType::Delta
255        } else if uniqueness_ratio < 0.1 {
256            // Many duplicate values - use run-length encoding
257            ColumnCompressionType::RunLength
258        } else if avg_size > 8 && uniqueness_ratio < 0.5 {
259            // Large values with moderate uniqueness - use dictionary
260            ColumnCompressionType::Dictionary
261        } else {
262            // Default to no compression
263            ColumnCompressionType::None
264        };
265
266        // Find existing column or create new one
267        if let Some(column) = self.columns.iter_mut().find(|c| c.name == column_name) {
268            column.compression = compression_type;
269        } else {
270            // Create new column with appropriate data type
271            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    /// Compress batches of rows for analytics workloads
292    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        // Process in batches
305        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        // Update stats
312        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            // Update rolling average
321            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    /// Decompress batch
335    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    /// Collect column statistics for analytics
369    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            // Try to extract numeric value if possible
389            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    /// Get optimal compression for column based on analytics
414    pub fn get_optimal_compression(&self, column_name: &str) -> ColumnCompressionType {
415        if let Some(stats) = self.stats.column_stats.get(column_name) {
416            // Analytics-based compression selection
417            let uniqueness_ratio = stats.unique_values as f64 / stats.value_count as f64;
418
419            if uniqueness_ratio < 0.01 {
420                // Very low cardinality - bitmap or run-length
421                ColumnCompressionType::Bitmap
422            } else if uniqueness_ratio < 0.1 {
423                // Low cardinality - dictionary
424                ColumnCompressionType::Dictionary
425            } else if let (Some(min), Some(max)) = (stats.min_value, stats.max_value) {
426                // Numeric data with range - frame of reference or delta
427                let range = max - min;
428                if range < 1000.0 {
429                    ColumnCompressionType::FrameOfReference
430                } else {
431                    ColumnCompressionType::Delta
432                }
433            } else {
434                // Mixed data - LZ4
435                ColumnCompressionType::Lz4
436            }
437        } else {
438            ColumnCompressionType::None
439        }
440    }
441
442    /// Compress row-based data to column format
443    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        // Store metadata
456        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        // Process each column
460        for (col_idx, column) in self.columns.iter().enumerate() {
461            let mut column_data = Vec::new();
462
463            // Extract column data from all rows
464            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            // Compress column data
470            let compressed_column = self.compress_column_data(&column_data, column.compression)?;
471
472            // Store compressed column
473            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    /// Decompress column data back to rows
481    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        // Decompress each column
506        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        // Reconstruct rows
531        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    /// Extract cell data from row (simplified implementation)
546    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                // For simplicity, assume fixed-size cells of 8 bytes
559                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    /// Extract cell data for specific row from column data
572    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    /// Compress column data using specified compression
595    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                // For simplicity, fall back to no compression for other types
610                Ok(data.to_vec())
611            }
612        }
613    }
614
615    /// Decompress column data
616    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                // For simplicity, fall back to no compression for other types
631                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        // For simplicity, treat data as single row
652        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        // Create test data (2 columns x 8 bytes each = 16 bytes per row)
716        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        // Create 20 rows
785        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], // duplicate
802            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); // 1, 2, 3
813        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        // Low cardinality data
821        let low_card_values: Vec<Vec<u8>> = (0..100)
822            .map(|i| {
823                let val = (i % 5) as u64; // Only 5 unique values
824                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        // Should use dictionary for low cardinality
831        assert!(matches!(
832            compression,
833            ColumnCompressionType::Dictionary | ColumnCompressionType::Bitmap
834        ));
835
836        // High cardinality numeric data
837        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        // Should use delta or frame-of-reference for sequential numeric
847        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        // Small range values
913        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        // Small range should use FrameOfReference
930        assert_eq!(compression, ColumnCompressionType::FrameOfReference);
931    }
932
933    #[test]
934    fn test_column_compression_types() {
935        // Test all compression types are distinct
936        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}