use vortex_array::ArrayRef;
use vortex_array::ExecutionCtx;
use vortex_array::IntoArray;
use vortex_array::aggregate_fn::fns::is_constant::is_constant;
use vortex_array::arrays::ConstantArray;
use vortex_array::arrays::MaskedArray;
use vortex_array::dtype::DType;
use vortex_array::scalar::Scalar;
use vortex_error::VortexResult;
use crate::scheme::SchemeId;
use crate::stats::ArrayAndStats;
pub(crate) const CONSTANT_SCHEME_ID: SchemeId = SchemeId {
name: "vortex.compressor.constant",
};
pub(crate) fn is_constant_for_compression(
data: &ArrayAndStats,
exec_ctx: &mut ExecutionCtx,
) -> VortexResult<bool> {
let dtype = data.array().dtype();
if matches!(dtype, DType::Bool(_)) {
return Ok(data.bool_stats(exec_ctx).is_constant());
}
if dtype.is_int() {
let stats = data.integer_stats(exec_ctx);
if let Some(distinct_count) = stats.distinct_count() {
return Ok(distinct_count == 1);
}
return Ok(stats.erased().max_minus_min() == 0);
}
if dtype.is_float() {
let stats = data.float_stats(exec_ctx);
if let Some(distinct_count) = stats.distinct_count() {
return Ok(distinct_count == 1);
}
return is_constant(data.array(), exec_ctx);
}
if dtype.is_utf8() || dtype.is_binary() {
let stats = data.varbinview_stats(exec_ctx);
if stats.estimated_distinct_count().is_some_and(|c| c > 1) {
return Ok(false);
}
return is_constant(data.array(), exec_ctx);
}
is_constant(data.array(), exec_ctx)
}
pub(crate) fn compress_constant(
source: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
let validity = source.validity()?;
let mask = validity.execute_mask(source.len(), ctx)?;
let Some(first_valid) = mask.first() else {
return Ok(
ConstantArray::new(Scalar::null(source.dtype().clone()), source.len()).into_array(),
);
};
let scalar = source.execute_scalar(first_valid, ctx)?;
let const_arr = ConstantArray::new(scalar, source.len()).into_array();
if mask.all_true() {
Ok(const_arr)
} else {
Ok(MaskedArray::try_new(const_arr, validity)?.into_array())
}
}
#[cfg(test)]
mod tests {
use std::sync::LazyLock;
use vortex_array::IntoArray;
use vortex_array::VortexSessionExecute;
use vortex_array::arrays::BoolArray;
use vortex_array::arrays::Constant;
use vortex_array::arrays::DecimalArray;
use vortex_array::arrays::Masked;
use vortex_array::arrays::PrimitiveArray;
use vortex_array::arrays::TemporalArray;
use vortex_array::arrays::VarBinViewArray;
use vortex_array::dtype::DecimalDType;
use vortex_array::extension::datetime::TimeUnit;
use vortex_array::validity::Validity;
use vortex_buffer::buffer;
use vortex_error::VortexResult;
use vortex_session::VortexSession;
use crate::CascadingCompressor;
static SESSION: LazyLock<VortexSession> = LazyLock::new(vortex_array::array_session);
fn empty_compressor() -> CascadingCompressor {
CascadingCompressor::new(Vec::new())
}
#[test]
fn constant_int_compresses_without_schemes() -> VortexResult<()> {
let array = PrimitiveArray::new(buffer![7i64; 100], Validity::NonNullable).into_array();
let mut ctx = SESSION.create_execution_ctx();
let compressed = empty_compressor().compress(&array, &mut ctx)?;
assert!(compressed.is::<Constant>());
Ok(())
}
#[test]
fn constant_int_with_nulls_compresses_to_masked_constant() -> VortexResult<()> {
let validity =
Validity::Array(BoolArray::from_iter((0..100).map(|i| i % 10 != 0)).into_array());
let array = PrimitiveArray::new(buffer![7i64; 100], validity).into_array();
let mut ctx = SESSION.create_execution_ctx();
let compressed = empty_compressor().compress(&array, &mut ctx)?;
assert!(compressed.is::<Masked>());
Ok(())
}
#[test]
fn constant_string_compresses_without_schemes() -> VortexResult<()> {
let array = VarBinViewArray::from_iter_str(std::iter::repeat_n("hello", 100)).into_array();
let mut ctx = SESSION.create_execution_ctx();
let compressed = empty_compressor().compress(&array, &mut ctx)?;
assert!(compressed.is::<Constant>());
Ok(())
}
#[test]
fn constant_bool_compresses_without_schemes() -> VortexResult<()> {
let array = BoolArray::from_iter(std::iter::repeat_n(true, 100)).into_array();
let mut ctx = SESSION.create_execution_ctx();
let compressed = empty_compressor().compress(&array, &mut ctx)?;
assert!(compressed.is::<Constant>());
Ok(())
}
#[test]
fn constant_decimal_compresses_without_schemes() -> VortexResult<()> {
let array = DecimalArray::new(
buffer![123_456i128; 100],
DecimalDType::new(20, 2),
Validity::NonNullable,
)
.into_array();
let mut ctx = SESSION.create_execution_ctx();
let compressed = empty_compressor().compress(&array, &mut ctx)?;
assert!(compressed.is::<Constant>());
Ok(())
}
#[test]
fn constant_timestamp_compresses_without_schemes() -> VortexResult<()> {
let ts = PrimitiveArray::from_iter(std::iter::repeat_n(1_704_067_200_000i64, 100));
let array = TemporalArray::new_timestamp(ts.into_array(), TimeUnit::Milliseconds, None)
.into_array();
let mut ctx = SESSION.create_execution_ctx();
let compressed = empty_compressor().compress(&array, &mut ctx)?;
assert!(compressed.is::<Constant>());
Ok(())
}
#[test]
fn non_constant_int_is_left_canonical_without_schemes() -> VortexResult<()> {
let array = PrimitiveArray::from_iter(0..100i64).into_array();
let mut ctx = SESSION.create_execution_ctx();
let compressed = empty_compressor().compress(&array, &mut ctx)?;
assert!(!compressed.is::<Constant>());
assert_eq!(compressed.dtype(), array.dtype());
Ok(())
}
}