use vortex_array::ArrayRef;
use vortex_array::ArrayView;
use vortex_array::Canonical;
use vortex_array::Columnar;
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::arrays::VarBinView;
use vortex_array::arrays::bool::BoolArrayExt;
use vortex_array::arrays::extension::ExtensionArrayExt;
use vortex_array::arrays::varbinview::Ref;
use vortex_array::dtype::NativePType;
use vortex_array::match_each_decimal_value_type;
use vortex_array::match_each_native_ptype;
use vortex_array::scalar::Scalar;
use vortex_error::VortexResult;
use vortex_mask::Mask;
use crate::scheme::SchemeId;
use crate::trace;
const CONSTANT_SCHEME_ID: SchemeId = SchemeId {
name: "vortex.compressor.constant",
};
pub(crate) fn is_constant_for_compression(
array: &ArrayRef,
ctx: &mut ExecutionCtx,
) -> VortexResult<bool> {
let mask = array.validity()?.execute_mask(array.len(), ctx)?;
let Some(first) = mask.first() else {
return Ok(!array.is_empty());
};
Ok(match array.clone().execute::<Columnar>(ctx)? {
Columnar::Constant(_) => true,
Columnar::Canonical(Canonical::Extension(array)) => {
is_constant_for_compression(array.storage_array(), ctx)?
}
Columnar::Canonical(_) if mask.all_true() => is_constant(array, ctx)?,
Columnar::Canonical(Canonical::Primitive(array)) => {
match_each_native_ptype!(array.ptype(), |T| {
let values = array.as_slice::<T>();
let first = values[first];
all_valid_match(&mask, |i| values[i].is_eq(first))
})
}
Columnar::Canonical(Canonical::Bool(array)) => {
let Mask::Values(valid) = &mask else {
unreachable!("all-valid and all-null masks handled above");
};
let values = array.bit_buffer_view();
let expected = if values.value(first) { u64::MAX } else { 0 };
values
.chunks()
.iter_padded()
.zip(valid.bit_buffer().chunks().iter_padded())
.all(|(values, valid)| ((values ^ expected) & valid) == 0)
}
Columnar::Canonical(Canonical::Decimal(array)) => {
match_each_decimal_value_type!(array.values_type(), |T| {
let values = array.buffer::<T>();
let first = values[first];
all_valid_match(&mask, |i| values[i] == first)
})
}
Columnar::Canonical(Canonical::VarBinView(array)) => {
is_constant_varbinview(array.as_view(), &mask, first)
}
_ => false,
})
}
fn all_valid_match(mask: &Mask, mut matches: impl FnMut(usize) -> bool) -> bool {
match mask {
Mask::AllTrue(len) => (0..*len).all(matches),
Mask::AllFalse(_) => true,
Mask::Values(valid) => valid
.bit_buffer()
.try_for_each_set_index(|i| if matches(i) { Ok(()) } else { Err(()) })
.is_ok(),
}
}
fn is_constant_varbinview(array: ArrayView<'_, VarBinView>, mask: &Mask, first: usize) -> bool {
let views = array.views();
let first = &views[first];
if first.is_inlined() {
return all_valid_match(mask, |i| {
let view = &views[i];
view == first
|| (view.is_inlined() && view.as_inlined().value() == first.as_inlined().value())
});
}
let first = first.as_view();
let buffers = array
.data_buffers()
.iter()
.map(|buffer| buffer.as_host())
.collect::<Vec<_>>();
let bytes = |view: &Ref| &buffers[view.buffer_index as usize][view.as_range()];
let first_bytes = bytes(first);
all_valid_match(mask, |i| {
let view = &views[i];
if view.len() != first.size {
return false;
}
let view = view.as_view();
view.prefix == first.prefix
&& ((view.buffer_index == first.buffer_index && view.offset == first.offset)
|| bytes(view) == first_bytes)
})
}
pub(crate) fn compress_as_constant(
array: ArrayRef,
exec_ctx: &mut ExecutionCtx,
) -> VortexResult<ArrayRef> {
let before_nbytes = array.nbytes();
let _winner_span = trace::winner_compress_span(CONSTANT_SCHEME_ID, before_nbytes).entered();
let compressed = compress_constant(&array, exec_ctx)?;
let after_nbytes = compressed.nbytes();
let actual_ratio = (after_nbytes != 0).then(|| before_nbytes as f64 / after_nbytes as f64);
let accepted = after_nbytes < before_nbytes;
trace::record_winner_compress_result(after_nbytes, None, actual_ratio, accepted);
Ok(if accepted { compressed } else { array })
}
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;