use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionCodec {
None,
Dictionary,
EnhancedDictionary,
ZstdLevel1,
ZstdLevel3,
ZstdLevel6,
AdaptiveZstd,
RLE,
Delta,
DoubleDelta,
BitPack,
}
impl fmt::Display for CompressionCodec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CompressionCodec::None => write!(f, "None"),
CompressionCodec::Dictionary => write!(f, "Dictionary"),
CompressionCodec::EnhancedDictionary => write!(f, "EnhancedDict"),
CompressionCodec::ZstdLevel1 => write!(f, "Zstd-L1"),
CompressionCodec::ZstdLevel3 => write!(f, "Zstd-L3"),
CompressionCodec::ZstdLevel6 => write!(f, "Zstd-L6"),
CompressionCodec::AdaptiveZstd => write!(f, "AdaptiveZstd"),
CompressionCodec::RLE => write!(f, "RLE"),
CompressionCodec::Delta => write!(f, "Delta"),
CompressionCodec::DoubleDelta => write!(f, "DoubleDelta"),
CompressionCodec::BitPack => write!(f, "BitPack"),
}
}
}
#[derive(Debug, Clone)]
pub struct ColumnProfile {
pub data_type: DataType,
pub cardinality: usize,
pub cardinality_ratio: f64,
pub null_ratio: f64,
pub repetition_ratio: f64, pub is_sorted: bool,
pub min_value: Option<f64>,
pub max_value: Option<f64>,
pub sample_entropy: f64, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataType {
String,
Int32,
Int64,
Float32,
Float64,
Boolean,
Date,
Timestamp,
Binary,
}
pub struct CodecSelector;
impl CodecSelector {
pub fn select(profile: &ColumnProfile) -> CompressionCodec {
match profile.data_type {
DataType::String => Self::select_string_codec(profile),
DataType::Boolean => CompressionCodec::BitPack,
DataType::Date | DataType::Timestamp => Self::select_temporal_codec(profile),
_ => Self::select_numeric_codec(profile),
}
}
pub fn select_optimal_codec(profile: &ColumnProfile) -> CompressionCodec {
Self::select(profile)
}
fn select_string_codec(profile: &ColumnProfile) -> CompressionCodec {
if profile.cardinality_ratio > 0.5 {
return CompressionCodec::AdaptiveZstd; }
if profile.cardinality_ratio < 0.001 {
return CompressionCodec::EnhancedDictionary; }
if profile.cardinality_ratio < 0.01 {
return CompressionCodec::Dictionary;
}
CompressionCodec::EnhancedDictionary
}
fn select_numeric_codec(profile: &ColumnProfile) -> CompressionCodec {
if profile.is_sorted {
return CompressionCodec::DoubleDelta; }
if profile.repetition_ratio > 0.5 {
return CompressionCodec::RLE;
}
CompressionCodec::AdaptiveZstd
}
fn select_temporal_codec(profile: &ColumnProfile) -> CompressionCodec {
CompressionCodec::Delta
}
pub fn select_best(profile: &ColumnProfile, sample_data: &[u8]) -> CompressionCodec {
if sample_data.is_empty() {
return CompressionCodec::None;
}
let candidates = match profile.data_type {
DataType::String => vec![
CompressionCodec::Dictionary,
CompressionCodec::ZstdLevel3,
],
DataType::Boolean => vec![CompressionCodec::BitPack],
_ => vec![
CompressionCodec::Delta,
CompressionCodec::ZstdLevel3,
CompressionCodec::RLE,
],
};
candidates
.into_iter()
.max_by_key(|_| 1) .unwrap_or(CompressionCodec::None)
}
pub fn estimated_ratio(codec: CompressionCodec, profile: &ColumnProfile) -> f64 {
match codec {
CompressionCodec::Dictionary => {
if profile.cardinality > 0 {
1.0 / (profile.cardinality as f64).log2()
} else {
0.5
}
}
CompressionCodec::EnhancedDictionary => {
let standard = if profile.cardinality > 0 {
1.0 / (profile.cardinality as f64).log2()
} else {
0.5
};
(standard * 0.975).max(0.0) }
CompressionCodec::ZstdLevel1 => 0.6, CompressionCodec::ZstdLevel3 => 0.36, CompressionCodec::ZstdLevel6 => 0.30, CompressionCodec::AdaptiveZstd => 0.34, CompressionCodec::RLE => {
1.0 - profile.repetition_ratio
}
CompressionCodec::Delta => 0.25, CompressionCodec::DoubleDelta => 0.22, CompressionCodec::BitPack => 0.125, CompressionCodec::None => 1.0, }
}
pub fn codec_to_id(codec: CompressionCodec) -> u8 {
use crate::decompression::CodecId;
match codec {
CompressionCodec::None => CodecId::None as u8,
CompressionCodec::Dictionary => CodecId::Dictionary as u8,
CompressionCodec::EnhancedDictionary => CodecId::EnhancedDictionary as u8,
CompressionCodec::ZstdLevel1 => CodecId::LZSS as u8, CompressionCodec::ZstdLevel3 => CodecId::LZSS as u8,
CompressionCodec::ZstdLevel6 => CodecId::LZSS as u8,
CompressionCodec::AdaptiveZstd => CodecId::LZSS as u8,
CompressionCodec::RLE => CodecId::RLE as u8,
CompressionCodec::Delta => CodecId::FOR as u8,
CompressionCodec::DoubleDelta => CodecId::DoubleDelta as u8,
CompressionCodec::BitPack => CodecId::FOR as u8, }
}
}
#[derive(Debug, Clone)]
pub struct CodecRecommendation {
pub primary: CompressionCodec,
pub secondary: CompressionCodec,
pub estimated_ratio: f64,
pub reasoning: String,
}
impl CodecRecommendation {
pub fn analyze(profile: &ColumnProfile) -> Self {
let primary = CodecSelector::select(profile);
let estimated_ratio = CodecSelector::estimated_ratio(primary, profile);
let reasoning = match primary {
CompressionCodec::Dictionary => {
format!("Low cardinality ({:.1}%), dictionary efficient",
profile.cardinality_ratio * 100.0)
}
CompressionCodec::ZstdLevel3 => {
format!("High cardinality ({:.1}%), Zstd optimal",
profile.cardinality_ratio * 100.0)
}
CompressionCodec::Delta => {
"Temporal/sorted data, delta optimal".to_string()
}
CompressionCodec::BitPack => {
"Boolean type, bit-packing efficient".to_string()
}
_ => format!("{:?} codec selected", primary),
};
Self {
primary,
secondary: CompressionCodec::ZstdLevel3,
estimated_ratio,
reasoning,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_codec_selection() {
let low_card = ColumnProfile {
data_type: DataType::String,
cardinality: 10,
cardinality_ratio: 0.001,
null_ratio: 0.0,
repetition_ratio: 0.99,
is_sorted: false,
min_value: None,
max_value: None,
sample_entropy: 1.0,
};
assert_eq!(
CodecSelector::select(&low_card),
CompressionCodec::Dictionary
);
}
#[test]
fn test_boolean_codec_selection() {
let bool_profile = ColumnProfile {
data_type: DataType::Boolean,
cardinality: 2,
cardinality_ratio: 0.0,
null_ratio: 0.0,
repetition_ratio: 1.0,
is_sorted: false,
min_value: None,
max_value: None,
sample_entropy: 1.0,
};
assert_eq!(
CodecSelector::select(&bool_profile),
CompressionCodec::BitPack
);
}
#[test]
fn test_codec_recommendation() {
let profile = ColumnProfile {
data_type: DataType::String,
cardinality: 5,
cardinality_ratio: 0.005,
null_ratio: 0.0,
repetition_ratio: 0.99,
is_sorted: false,
min_value: None,
max_value: None,
sample_entropy: 1.5,
};
let rec = CodecRecommendation::analyze(&profile);
assert_eq!(rec.primary, CompressionCodec::Dictionary);
assert!(rec.estimated_ratio > 0.0 && rec.estimated_ratio < 1.0);
}
}