use vortex_array::ArrayId;
use vortex_decimal_byte_parts::decimal_byte_parts_v2_id;
use vortex_fastlanes::bitpacked_v2_id;
use vortex_fastlanes::for_v2_id;
use vortex_session::VortexSession;
use vortex_utils::aliases::hash_set::HashSet;
use crate::BtrBlocksCompressor;
use crate::CascadingCompressor;
use crate::CompressionSessionExt;
use crate::Scheme;
use crate::SchemeExt;
use crate::SchemeId;
use crate::allowed_ids::AllowedIds;
use crate::schemes::binary;
use crate::schemes::float;
use crate::schemes::integer;
use crate::schemes::string;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CompressionMode {
All,
Default,
Compact,
Cuda,
}
impl CompressionMode {
fn excluded_schemes(self) -> Vec<SchemeId> {
let mut excluded = Vec::new();
match self {
Self::All => {}
Self::Default => {
#[cfg(feature = "zstd")]
excluded.extend([
string::ZstdScheme.id(),
binary::ZstdScheme.id(),
binary::ZstdBuffersScheme.id(),
]);
#[cfg(feature = "pco")]
excluded.extend([integer::PcoScheme.id(), float::PcoScheme.id()]);
}
Self::Compact => {
#[cfg(feature = "zstd")]
excluded.push(binary::ZstdBuffersScheme.id());
}
Self::Cuda => {
excluded.extend([
integer::DeltaScheme::default().id(),
integer::SparseScheme.id(),
integer::IntRLEScheme.id(),
float::ALPRDScheme.id(),
float::FloatRLEScheme.id(),
float::NullDominatedSparseScheme.id(),
string::NullDominatedSparseScheme.id(),
string::StringDictScheme.id(),
binary::BinaryDictScheme.id(),
]);
#[cfg(feature = "zstd")]
excluded.push(string::ZstdScheme.id());
#[cfg(feature = "pco")]
excluded.extend([integer::PcoScheme.id(), float::PcoScheme.id()]);
}
}
excluded
}
fn excluded_encodings(self) -> Vec<ArrayId> {
match self {
Self::All | Self::Default | Self::Compact => Vec::new(),
Self::Cuda => vec![decimal_byte_parts_v2_id(), for_v2_id(), bitpacked_v2_id()],
}
}
}
#[derive(Debug, Clone)]
pub struct BtrBlocksCompressorBuilder {
schemes: Vec<&'static dyn Scheme>,
allowed: AllowedIds,
mode: CompressionMode,
}
impl BtrBlocksCompressorBuilder {
pub fn from_session(session: &VortexSession) -> Self {
Self {
schemes: session.compression().schemes().to_vec(),
allowed: AllowedIds::from_session(session),
mode: CompressionMode::Default,
}
}
pub fn empty() -> Self {
Self {
schemes: Vec::new(),
allowed: AllowedIds::all(),
mode: CompressionMode::All,
}
}
pub fn with_new_scheme(mut self, scheme: &'static dyn Scheme) -> Self {
assert!(
!self.schemes.iter().any(|s| s.id() == scheme.id()),
"scheme {:?} is already present in the builder",
scheme.id(),
);
self.schemes.push(scheme);
self
}
pub fn with_compact(mut self) -> Self {
self.mode = CompressionMode::Compact;
self
}
pub fn only_cuda_compatible(mut self) -> Self {
self.mode = CompressionMode::Cuda;
self
}
pub fn exclude_schemes(mut self, ids: impl IntoIterator<Item = SchemeId>) -> Self {
let ids: HashSet<_> = ids.into_iter().collect();
self.schemes.retain(|s| !ids.contains(&s.id()));
self
}
pub fn disable_editions(mut self) -> Self {
self.allowed.editions = None;
self
}
pub fn unrestricted(mut self) -> Self {
self.allowed.registered = None;
self.allowed.editions = None;
self
}
pub fn build(self) -> BtrBlocksCompressor {
BtrBlocksCompressor(CascadingCompressor::new(self.allowed_schemes()))
}
fn allowed_schemes(&self) -> Vec<&'static dyn Scheme> {
let excluded: HashSet<SchemeId> = self.mode.excluded_schemes().into_iter().collect();
let excluded_ids: HashSet<ArrayId> = self.mode.excluded_encodings().into_iter().collect();
let allowed = |id: &ArrayId| self.allowed.is_allowed(id) && !excluded_ids.contains(id);
self.schemes
.iter()
.copied()
.filter(|s| !excluded.contains(&s.id()))
.map(|s| s.refine(&allowed))
.filter(|s| s.produced_encodings().iter().all(&allowed))
.collect()
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use vortex_array::ArrayId;
use vortex_array::VTable;
use vortex_array::arrays::Patched;
use vortex_array::arrays::VarBin;
use vortex_decimal_byte_parts::DecimalByteParts;
use vortex_decimal_byte_parts::decimal_byte_parts_v1_id;
use vortex_fastlanes::bitpacked_v1_id;
use vortex_fastlanes::bitpacked_v2_id;
use vortex_fsst::FSST;
use super::*;
use crate::CompressionSession;
use crate::schemes::decimal::DECIMAL_V1;
use crate::schemes::decimal::DECIMAL_V2;
#[rstest]
#[case::fsst_missing_codes(&string::FSSTScheme, vec![FSST.id()], false)]
#[case::fsst_with_codes(&string::FSSTScheme, vec![FSST.id(), VarBin.id()], true)]
#[case::decimal_v1_id(&DECIMAL_V1, vec![decimal_byte_parts_v1_id()], true)]
#[case::decimal_both_ids(&DECIMAL_V2, vec![decimal_byte_parts_v1_id(), DecimalByteParts.id()], true)]
#[case::bitpacking_v1_id(
&integer::BITPACKING_V1,
vec![bitpacked_v1_id(), Patched.id()],
true
)]
#[case::bitpacking_both_ids(
&integer::BITPACKING_V1,
vec![bitpacked_v1_id(), bitpacked_v2_id(), Patched.id()],
true
)]
#[case::bitpacking_v2_only(
&integer::BITPACKING_V1,
vec![bitpacked_v2_id(), Patched.id()],
false
)]
fn test_allowed_schemes(
#[case] scheme: &'static dyn Scheme,
#[case] allowed: Vec<ArrayId>,
#[case] retained: bool,
) {
let mut builder = BtrBlocksCompressorBuilder::empty().with_new_scheme(scheme);
builder.allowed.editions = Some(allowed.into_iter().collect());
assert_eq!(!builder.allowed_schemes().is_empty(), retained);
}
#[test]
fn delta_is_excluded_only_by_cuda() {
let session = VortexSession::empty().with::<CompressionSession>();
let builder = BtrBlocksCompressorBuilder::from_session(&session).unrestricted();
let has_delta = |builder: BtrBlocksCompressorBuilder| {
builder
.allowed_schemes()
.iter()
.any(|s| s.id() == integer::DeltaScheme::default().id())
};
assert!(has_delta(builder.clone()));
assert!(has_delta(builder.clone().with_compact()));
assert!(!has_delta(builder.only_cuda_compatible()));
}
}