From ae3bb13bed4c1a12557a46e7197f0a57baada5e1 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 12:35:52 -0700 Subject: [PATCH 1/9] is-constant Signed-off-by: Nicholas Gates --- .../datetime-parts/src/compute/is_constant.rs | 76 +- encodings/datetime-parts/src/compute/mod.rs | 2 +- encodings/datetime-parts/src/lib.rs | 17 + .../decimal_byte_parts/compute/is_constant.rs | 66 +- .../src/decimal_byte_parts/compute/mod.rs | 2 +- .../src/decimal_byte_parts/mod.rs | 2 +- encodings/decimal-byte-parts/src/lib.rs | 19 + .../src/bitpacking/compute/is_constant.rs | 83 ++- .../fastlanes/src/bitpacking/compute/mod.rs | 2 +- encodings/fastlanes/src/bitpacking/mod.rs | 2 +- .../fastlanes/src/for/compute/is_constant.rs | 64 +- encodings/fastlanes/src/for/compute/mod.rs | 2 +- encodings/fastlanes/src/for/mod.rs | 2 +- encodings/fastlanes/src/lib.rs | 28 + encodings/runend/src/compute/is_constant.rs | 73 +- encodings/runend/src/compute/mod.rs | 2 +- encodings/runend/src/lib.rs | 8 +- encodings/runend/src/ops.rs | 18 +- .../src/aggregate_fn/fns/is_constant/bool.rs | 9 + .../aggregate_fn/fns/is_constant/decimal.rs | 13 + .../src/aggregate_fn/fns/is_constant/mod.rs | 649 ++++++++++++++++++ .../fns/is_constant/primitive.rs} | 37 +- .../aggregate_fn/fns/is_constant/varbin.rs | 45 ++ vortex-array/src/aggregate_fn/fns/mod.rs | 1 + vortex-array/src/aggregate_fn/kernels.rs | 4 +- vortex-array/src/aggregate_fn/session.rs | 3 + .../src/arrays/bool/compute/is_constant.rs | 47 -- vortex-array/src/arrays/bool/compute/mod.rs | 1 - .../src/arrays/chunked/compute/aggregate.rs | 4 +- .../src/arrays/chunked/compute/is_constant.rs | 85 --- .../src/arrays/chunked/compute/mod.rs | 1 - .../src/arrays/decimal/compute/is_constant.rs | 58 -- .../src/arrays/decimal/compute/mod.rs | 1 - .../src/arrays/dict/compute/is_constant.rs | 75 +- vortex-array/src/arrays/dict/compute/mod.rs | 2 +- .../arrays/extension/compute/is_constant.rs | 24 - .../src/arrays/extension/compute/mod.rs | 1 - .../fixed_size_list/compute/is_constant.rs | 44 -- .../src/arrays/fixed_size_list/compute/mod.rs | 1 - .../src/arrays/list/compute/is_constant.rs | 221 ------ vortex-array/src/arrays/list/compute/mod.rs | 1 - .../arrays/listview/compute/is_constant.rs | 52 -- .../src/arrays/listview/compute/mod.rs | 1 - .../src/arrays/listview/tests/operations.rs | 19 +- .../src/arrays/primitive/compute/mod.rs | 3 - vortex-array/src/arrays/primitive/mod.rs | 2 - .../src/arrays/struct_/compute/is_constant.rs | 38 - .../src/arrays/struct_/compute/mod.rs | 10 +- .../src/arrays/varbin/compute/is_constant.rs | 39 -- vortex-array/src/arrays/varbin/compute/mod.rs | 1 - .../arrays/varbinview/compute/is_constant.rs | 63 -- .../src/arrays/varbinview/compute/mod.rs | 1 - vortex-array/src/compute/is_constant.rs | 314 +-------- vortex-array/src/compute/mod.rs | 6 +- vortex-array/src/stats/array.rs | 4 +- vortex-btrblocks/src/canonical_compressor.rs | 14 +- vortex-btrblocks/src/compressor/string.rs | 7 +- vortex-file/src/lib.rs | 15 +- 58 files changed, 1195 insertions(+), 1189 deletions(-) create mode 100644 vortex-array/src/aggregate_fn/fns/is_constant/bool.rs create mode 100644 vortex-array/src/aggregate_fn/fns/is_constant/decimal.rs create mode 100644 vortex-array/src/aggregate_fn/fns/is_constant/mod.rs rename vortex-array/src/{arrays/primitive/compute/is_constant.rs => aggregate_fn/fns/is_constant/primitive.rs} (58%) create mode 100644 vortex-array/src/aggregate_fn/fns/is_constant/varbin.rs delete mode 100644 vortex-array/src/arrays/bool/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/chunked/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/decimal/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/extension/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/fixed_size_list/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/list/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/listview/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/struct_/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/varbin/compute/is_constant.rs delete mode 100644 vortex-array/src/arrays/varbinview/compute/is_constant.rs diff --git a/encodings/datetime-parts/src/compute/is_constant.rs b/encodings/datetime-parts/src/compute/is_constant.rs index dd66a7be2a4..a9098461111 100644 --- a/encodings/datetime-parts/src/compute/is_constant.rs +++ b/encodings/datetime-parts/src/compute/is_constant.rs @@ -1,45 +1,65 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -use vortex_array::compute::IsConstantKernel; -use vortex_array::compute::IsConstantKernelAdapter; -use vortex_array::compute::IsConstantOpts; -use vortex_array::compute::is_constant_opts; -use vortex_array::register_kernel; +use vortex_array::ArrayRef; +use vortex_array::DynArray; +use vortex_array::ExecutionCtx; +use vortex_array::aggregate_fn::AggregateFnRef; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; +use vortex_array::aggregate_fn::fns::is_constant::is_constant; +use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; +use vortex_array::aggregate_fn::kernels::DynAggregateKernel; +use vortex_array::dtype::Nullability; +use vortex_array::scalar::Scalar; use vortex_error::VortexResult; use crate::DateTimeParts; -use crate::DateTimePartsArray; -impl IsConstantKernel for DateTimeParts { - fn is_constant( +/// DateTimeParts-specific is_constant kernel. +/// +/// Checks each component (days, seconds, subseconds) individually. +#[derive(Debug)] +pub(crate) struct DateTimePartsIsConstantKernel; + +impl DynAggregateKernel for DateTimePartsIsConstantKernel { + fn aggregate( &self, - array: &DateTimePartsArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - let Some(days) = is_constant_opts(array.days(), opts)? else { + aggregate_fn: &AggregateFnRef, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult> { + if !aggregate_fn.is::() { return Ok(None); - }; - if !days { - return Ok(Some(false)); } - let Some(seconds) = is_constant_opts(array.seconds(), opts)? else { + let Some(array) = batch.as_opt::() else { return Ok(None); }; - if !seconds { - return Ok(Some(false)); - } - let Some(subseconds) = is_constant_opts(array.subseconds(), opts)? else { - return Ok(None); - }; - if !subseconds { - return Ok(Some(false)); - } + let result = is_constant(array.days(), ctx)? + && is_constant(array.seconds(), ctx)? + && is_constant(array.subseconds(), ctx)?; + + let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); - Ok(Some(true)) + if result { + let first_value = if batch.is_empty() { + return Ok(Some(Scalar::null(partial_dtype))); + } else { + batch.scalar_at(0)?.into_nullable() + }; + Ok(Some(Scalar::struct_( + partial_dtype, + vec![Scalar::bool(true, Nullability::NonNullable), first_value], + ))) + } else { + Ok(Some(Scalar::struct_( + partial_dtype, + vec![ + Scalar::bool(false, Nullability::NonNullable), + Scalar::null(batch.dtype().as_nullable()), + ], + ))) + } } } - -register_kernel!(IsConstantKernelAdapter(DateTimeParts).lift()); diff --git a/encodings/datetime-parts/src/compute/mod.rs b/encodings/datetime-parts/src/compute/mod.rs index ae9842911d3..46ce9cf89e3 100644 --- a/encodings/datetime-parts/src/compute/mod.rs +++ b/encodings/datetime-parts/src/compute/mod.rs @@ -4,7 +4,7 @@ mod cast; mod compare; mod filter; -mod is_constant; +pub(crate) mod is_constant; pub(crate) mod kernel; mod mask; pub(super) mod rules; diff --git a/encodings/datetime-parts/src/lib.rs b/encodings/datetime-parts/src/lib.rs index fa0dbc01fe9..6394ad4e822 100644 --- a/encodings/datetime-parts/src/lib.rs +++ b/encodings/datetime-parts/src/lib.rs @@ -11,6 +11,23 @@ mod compute; mod ops; mod timestamp; +use vortex_array::aggregate_fn::AggregateFnVTable; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; +use vortex_array::aggregate_fn::session::AggregateFnSessionExt; +use vortex_array::session::ArraySessionExt; +use vortex_session::VortexSession; + +/// Initialize datetime-parts encoding in the given session. +pub fn initialize(session: &mut VortexSession) { + session.arrays().register(DateTimeParts::ID, DateTimeParts); + + session.aggregate_fns().register_aggregate_kernel( + DateTimeParts::ID, + Some(IsConstant.id()), + &compute::is_constant::DateTimePartsIsConstantKernel, + ); +} + #[cfg(test)] mod test { use vortex_array::ProstMetadata; diff --git a/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/is_constant.rs b/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/is_constant.rs index 835b0639559..b73abb782bc 100644 --- a/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/is_constant.rs +++ b/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/is_constant.rs @@ -1,24 +1,62 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -use vortex_array::compute::IsConstantKernel; -use vortex_array::compute::IsConstantKernelAdapter; -use vortex_array::compute::IsConstantOpts; -use vortex_array::compute::is_constant_opts; -use vortex_array::register_kernel; +use vortex_array::ArrayRef; +use vortex_array::DynArray; +use vortex_array::ExecutionCtx; +use vortex_array::aggregate_fn::AggregateFnRef; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; +use vortex_array::aggregate_fn::fns::is_constant::is_constant; +use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; +use vortex_array::aggregate_fn::kernels::DynAggregateKernel; +use vortex_array::dtype::Nullability; +use vortex_array::scalar::Scalar; use vortex_error::VortexResult; use crate::DecimalByteParts; -use crate::DecimalBytePartsArray; -impl IsConstantKernel for DecimalByteParts { - fn is_constant( +/// DecimalByteParts-specific is_constant kernel. +/// +/// Delegates to checking if the MSP (most significant part) is constant. +#[derive(Debug)] +pub(crate) struct DecimalBytePartsIsConstantKernel; + +impl DynAggregateKernel for DecimalBytePartsIsConstantKernel { + fn aggregate( &self, - array: &DecimalBytePartsArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - is_constant_opts(&array.msp, opts) + aggregate_fn: &AggregateFnRef, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult> { + if !aggregate_fn.is::() { + return Ok(None); + } + + let Some(array) = batch.as_opt::() else { + return Ok(None); + }; + + let result = is_constant(array.msp(), ctx)?; + let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); + + if result { + let first_value = if batch.is_empty() { + return Ok(Some(Scalar::null(partial_dtype))); + } else { + batch.scalar_at(0)?.into_nullable() + }; + Ok(Some(Scalar::struct_( + partial_dtype, + vec![Scalar::bool(true, Nullability::NonNullable), first_value], + ))) + } else { + Ok(Some(Scalar::struct_( + partial_dtype, + vec![ + Scalar::bool(false, Nullability::NonNullable), + Scalar::null(batch.dtype().as_nullable()), + ], + ))) + } } } - -register_kernel!(IsConstantKernelAdapter(DecimalByteParts).lift()); diff --git a/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/mod.rs b/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/mod.rs index 7b0cf2cffcb..77ede1a4563 100644 --- a/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/mod.rs +++ b/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/mod.rs @@ -4,7 +4,7 @@ mod cast; mod compare; mod filter; -mod is_constant; +pub(crate) mod is_constant; pub(crate) mod kernel; mod mask; mod take; diff --git a/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs b/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs index b60f6516259..cd56c256383 100644 --- a/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs +++ b/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -mod compute; +pub(crate) mod compute; mod rules; mod slice; diff --git a/encodings/decimal-byte-parts/src/lib.rs b/encodings/decimal-byte-parts/src/lib.rs index e97cd6becef..6a7035bbcb6 100644 --- a/encodings/decimal-byte-parts/src/lib.rs +++ b/encodings/decimal-byte-parts/src/lib.rs @@ -3,6 +3,7 @@ mod decimal_byte_parts; +use decimal_byte_parts::compute::is_constant::DecimalBytePartsIsConstantKernel; /// This encoding allow compression of decimals using integer compression schemes. /// Decimals can be compressed by narrowing the signed decimal value into the smallest signed value, /// then integer compression if that is a value `ptype`, otherwise the decimal can be split into @@ -12,3 +13,21 @@ mod decimal_byte_parts; /// an i128 decimal could be converted into a [i64, u64] with further narrowing applied to either /// value. pub use decimal_byte_parts::*; +use vortex_array::aggregate_fn::AggregateFnVTable; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; +use vortex_array::aggregate_fn::session::AggregateFnSessionExt; +use vortex_array::session::ArraySessionExt; +use vortex_session::VortexSession; + +/// Initialize decimal-byte-parts encoding in the given session. +pub fn initialize(session: &mut VortexSession) { + session + .arrays() + .register(DecimalByteParts::ID, DecimalByteParts); + + session.aggregate_fns().register_aggregate_kernel( + DecimalByteParts::ID, + Some(IsConstant.id()), + &DecimalBytePartsIsConstantKernel, + ); +} diff --git a/encodings/fastlanes/src/bitpacking/compute/is_constant.rs b/encodings/fastlanes/src/bitpacking/compute/is_constant.rs index 653fe458fd6..8c13cf956d5 100644 --- a/encodings/fastlanes/src/bitpacking/compute/is_constant.rs +++ b/encodings/fastlanes/src/bitpacking/compute/is_constant.rs @@ -5,41 +5,75 @@ use std::ops::Range; use itertools::Itertools; use lending_iterator::LendingIterator; +use vortex_array::ArrayRef; +use vortex_array::DynArray; +use vortex_array::ExecutionCtx; use vortex_array::ToCanonical; +use vortex_array::aggregate_fn::AggregateFnRef; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; +use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; +use vortex_array::aggregate_fn::fns::is_constant::primitive::IS_CONST_LANE_WIDTH; +use vortex_array::aggregate_fn::fns::is_constant::primitive::compute_is_constant; +use vortex_array::aggregate_fn::kernels::DynAggregateKernel; use vortex_array::arrays::PrimitiveArray; -use vortex_array::arrays::primitive::IS_CONST_LANE_WIDTH; -use vortex_array::arrays::primitive::compute_is_constant; -use vortex_array::compute::IsConstantKernel; -use vortex_array::compute::IsConstantKernelAdapter; -use vortex_array::compute::IsConstantOpts; use vortex_array::dtype::IntegerPType; +use vortex_array::dtype::Nullability; use vortex_array::match_each_integer_ptype; use vortex_array::match_each_unsigned_integer_ptype; -use vortex_array::register_kernel; +use vortex_array::scalar::Scalar; use vortex_error::VortexResult; use crate::BitPacked; use crate::BitPackedArray; use crate::unpack_iter::BitPacked as BitPackedUnpack; -impl IsConstantKernel for BitPacked { - fn is_constant( +/// BitPacked-specific is_constant kernel with SIMD support. +#[derive(Debug)] +pub(crate) struct BitPackedIsConstantKernel; + +impl DynAggregateKernel for BitPackedIsConstantKernel { + fn aggregate( &self, - array: &BitPackedArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - if opts.is_negligible_cost() { + aggregate_fn: &AggregateFnRef, + batch: &ArrayRef, + _ctx: &mut ExecutionCtx, + ) -> VortexResult> { + if !aggregate_fn.is::() { + return Ok(None); + } + + let Some(array) = batch.as_opt::() else { return Ok(None); + }; + + let result = match_each_integer_ptype!(array.ptype(), |P| { + bitpacked_is_constant::() }>(array)? + }); + + let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); + + if result { + let first_value = if batch.is_empty() { + return Ok(Some(Scalar::null(partial_dtype))); + } else { + batch.scalar_at(0)?.into_nullable() + }; + Ok(Some(Scalar::struct_( + partial_dtype, + vec![Scalar::bool(true, Nullability::NonNullable), first_value], + ))) + } else { + Ok(Some(Scalar::struct_( + partial_dtype, + vec![ + Scalar::bool(false, Nullability::NonNullable), + Scalar::null(batch.dtype().as_nullable()), + ], + ))) } - match_each_integer_ptype!(array.ptype(), |P| { - bitpacked_is_constant::() }>(array) - }) - .map(Some) } } -register_kernel!(IsConstantKernelAdapter(BitPacked).lift()); - fn bitpacked_is_constant( array: &BitPackedArray, ) -> VortexResult { @@ -170,14 +204,19 @@ fn apply_patches_idx_typed( #[cfg(test)] mod tests { use vortex_array::IntoArray; - use vortex_array::compute::is_constant; + use vortex_array::LEGACY_SESSION; + use vortex_array::VortexSessionExecute; + use vortex_array::aggregate_fn::fns::is_constant::is_constant; use vortex_buffer::buffer; + use vortex_error::VortexResult; use crate::BitPackedArray; #[test] - fn is_constant_with_patches() { - let array = BitPackedArray::encode(&buffer![4; 1025].into_array(), 2).unwrap(); - assert!(is_constant(&array.into_array()).unwrap().unwrap()); + fn is_constant_with_patches() -> VortexResult<()> { + let array = BitPackedArray::encode(&buffer![4; 1025].into_array(), 2)?; + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert!(is_constant(&array.into_array(), &mut ctx)?); + Ok(()) } } diff --git a/encodings/fastlanes/src/bitpacking/compute/mod.rs b/encodings/fastlanes/src/bitpacking/compute/mod.rs index c36e6ddfc62..f17054fc081 100644 --- a/encodings/fastlanes/src/bitpacking/compute/mod.rs +++ b/encodings/fastlanes/src/bitpacking/compute/mod.rs @@ -3,7 +3,7 @@ mod cast; mod filter; -mod is_constant; +pub(crate) mod is_constant; mod slice; mod take; diff --git a/encodings/fastlanes/src/bitpacking/mod.rs b/encodings/fastlanes/src/bitpacking/mod.rs index eec02d8e117..5130836e424 100644 --- a/encodings/fastlanes/src/bitpacking/mod.rs +++ b/encodings/fastlanes/src/bitpacking/mod.rs @@ -8,7 +8,7 @@ pub use array::bitpack_compress; pub use array::bitpack_decompress; pub use array::unpack_iter; -mod compute; +pub(crate) mod compute; mod vtable; pub use vtable::BitPacked; diff --git a/encodings/fastlanes/src/for/compute/is_constant.rs b/encodings/fastlanes/src/for/compute/is_constant.rs index 01ffe4aafe4..d55da25b086 100644 --- a/encodings/fastlanes/src/for/compute/is_constant.rs +++ b/encodings/fastlanes/src/for/compute/is_constant.rs @@ -1,20 +1,62 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -use vortex_array::compute::IsConstantKernel; -use vortex_array::compute::IsConstantKernelAdapter; -use vortex_array::compute::IsConstantOpts; -use vortex_array::compute::is_constant_opts; -use vortex_array::register_kernel; +use vortex_array::ArrayRef; +use vortex_array::DynArray; +use vortex_array::ExecutionCtx; +use vortex_array::aggregate_fn::AggregateFnRef; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; +use vortex_array::aggregate_fn::fns::is_constant::is_constant; +use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; +use vortex_array::aggregate_fn::kernels::DynAggregateKernel; +use vortex_array::dtype::Nullability; +use vortex_array::scalar::Scalar; use vortex_error::VortexResult; use crate::FoR; -use crate::FoRArray; -impl IsConstantKernel for FoR { - fn is_constant(&self, array: &FoRArray, opts: &IsConstantOpts) -> VortexResult> { - is_constant_opts(array.encoded(), opts) +/// FoR-specific is_constant kernel. +/// +/// Delegates to checking if the encoded array is constant. +#[derive(Debug)] +pub(crate) struct FoRIsConstantKernel; + +impl DynAggregateKernel for FoRIsConstantKernel { + fn aggregate( + &self, + aggregate_fn: &AggregateFnRef, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult> { + if !aggregate_fn.is::() { + return Ok(None); + } + + let Some(array) = batch.as_opt::() else { + return Ok(None); + }; + + let result = is_constant(array.encoded(), ctx)?; + let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); + + if result { + let first_value = if batch.is_empty() { + return Ok(Some(Scalar::null(partial_dtype))); + } else { + batch.scalar_at(0)?.into_nullable() + }; + Ok(Some(Scalar::struct_( + partial_dtype, + vec![Scalar::bool(true, Nullability::NonNullable), first_value], + ))) + } else { + Ok(Some(Scalar::struct_( + partial_dtype, + vec![ + Scalar::bool(false, Nullability::NonNullable), + Scalar::null(batch.dtype().as_nullable()), + ], + ))) + } } } - -register_kernel!(IsConstantKernelAdapter(FoR).lift()); diff --git a/encodings/fastlanes/src/for/compute/mod.rs b/encodings/fastlanes/src/for/compute/mod.rs index 3fe943211bd..4eec5d9ed6f 100644 --- a/encodings/fastlanes/src/for/compute/mod.rs +++ b/encodings/fastlanes/src/for/compute/mod.rs @@ -3,7 +3,7 @@ mod cast; mod compare; -mod is_constant; +pub(crate) mod is_constant; mod is_sorted; use vortex_array::ArrayRef; diff --git a/encodings/fastlanes/src/for/mod.rs b/encodings/fastlanes/src/for/mod.rs index 553ce4748bf..1dfaf61114f 100644 --- a/encodings/fastlanes/src/for/mod.rs +++ b/encodings/fastlanes/src/for/mod.rs @@ -4,7 +4,7 @@ mod array; pub use array::FoRArray; -mod compute; +pub(crate) mod compute; mod vtable; pub use vtable::FoR; diff --git a/encodings/fastlanes/src/lib.rs b/encodings/fastlanes/src/lib.rs index b981175319a..60658e95d47 100644 --- a/encodings/fastlanes/src/lib.rs +++ b/encodings/fastlanes/src/lib.rs @@ -16,6 +16,34 @@ mod rle; pub(crate) const FL_CHUNK_SIZE: usize = 1024; +use bitpacking::compute::is_constant::BitPackedIsConstantKernel; +use r#for::compute::is_constant::FoRIsConstantKernel; +use vortex_array::aggregate_fn::AggregateFnVTable; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; +use vortex_array::aggregate_fn::session::AggregateFnSessionExt; +use vortex_array::session::ArraySessionExt; +use vortex_session::VortexSession; + +/// Initialize fastlanes encodings in the given session. +pub fn initialize(session: &mut VortexSession) { + session.arrays().register(BitPacked::ID, BitPacked); + session.arrays().register(Delta::ID, Delta); + session.arrays().register(FoR::ID, FoR); + session.arrays().register(RLE::ID, RLE); + + // Register the encoding-specific aggregate kernels. + session.aggregate_fns().register_aggregate_kernel( + BitPacked::ID, + Some(IsConstant.id()), + &BitPackedIsConstantKernel, + ); + session.aggregate_fns().register_aggregate_kernel( + FoR::ID, + Some(IsConstant.id()), + &FoRIsConstantKernel, + ); +} + #[cfg(test)] mod test { use std::sync::LazyLock; diff --git a/encodings/runend/src/compute/is_constant.rs b/encodings/runend/src/compute/is_constant.rs index cc800183830..547f387731c 100644 --- a/encodings/runend/src/compute/is_constant.rs +++ b/encodings/runend/src/compute/is_constant.rs @@ -1,33 +1,62 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +use vortex_array::ArrayRef; use vortex_array::DynArray; -use vortex_array::compute::IsConstantKernel; -use vortex_array::compute::IsConstantKernelAdapter; -use vortex_array::compute::IsConstantOpts; -use vortex_array::compute::is_constant_opts; -use vortex_array::expr::stats::Stat; -use vortex_array::register_kernel; +use vortex_array::ExecutionCtx; +use vortex_array::aggregate_fn::AggregateFnRef; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; +use vortex_array::aggregate_fn::fns::is_constant::is_constant; +use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; +use vortex_array::aggregate_fn::kernels::DynAggregateKernel; +use vortex_array::dtype::Nullability; +use vortex_array::scalar::Scalar; use vortex_error::VortexResult; use crate::RunEnd; -impl IsConstantKernel for RunEnd { - fn is_constant( +/// RunEnd-specific is_constant kernel. +/// +/// If the values array of a run-end array is constant, the entire array is constant. +#[derive(Debug)] +pub(crate) struct RunEndIsConstantKernel; + +impl DynAggregateKernel for RunEndIsConstantKernel { + fn aggregate( &self, - array: &Self::Array, - opts: &IsConstantOpts, - ) -> VortexResult> { - // If there are known to be me 0 len runs then we can check if constant on the values. - debug_assert_eq!( - array - .ends() - .statistics() - .compute_as::(Stat::IsStrictSorted), - Some(true) - ); - is_constant_opts(array.values(), opts) + aggregate_fn: &AggregateFnRef, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult> { + if !aggregate_fn.is::() { + return Ok(None); + } + + let Some(array) = batch.as_opt::() else { + return Ok(None); + }; + + let result = is_constant(array.values(), ctx)?; + let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); + + if result { + let first_value = if batch.is_empty() { + return Ok(Some(Scalar::null(partial_dtype))); + } else { + batch.scalar_at(0)?.into_nullable() + }; + Ok(Some(Scalar::struct_( + partial_dtype, + vec![Scalar::bool(true, Nullability::NonNullable), first_value], + ))) + } else { + Ok(Some(Scalar::struct_( + partial_dtype, + vec![ + Scalar::bool(false, Nullability::NonNullable), + Scalar::null(batch.dtype().as_nullable()), + ], + ))) + } } } - -register_kernel!(IsConstantKernelAdapter(RunEnd).lift()); diff --git a/encodings/runend/src/compute/mod.rs b/encodings/runend/src/compute/mod.rs index 38d45d0115d..ad554d97128 100644 --- a/encodings/runend/src/compute/mod.rs +++ b/encodings/runend/src/compute/mod.rs @@ -5,7 +5,7 @@ mod cast; mod compare; mod fill_null; pub(crate) mod filter; -mod is_constant; +pub(crate) mod is_constant; mod is_sorted; pub(crate) mod min_max; pub(crate) mod take; diff --git a/encodings/runend/src/lib.rs b/encodings/runend/src/lib.rs index e0e76de5f6f..29b71db8019 100644 --- a/encodings/runend/src/lib.rs +++ b/encodings/runend/src/lib.rs @@ -27,6 +27,7 @@ pub mod _benchmarking { } use vortex_array::aggregate_fn::AggregateFnVTable; +use vortex_array::aggregate_fn::fns::is_constant::IsConstant; use vortex_array::aggregate_fn::fns::min_max::MinMax; use vortex_array::aggregate_fn::session::AggregateFnSessionExt; use vortex_array::session::ArraySessionExt; @@ -36,12 +37,17 @@ use vortex_session::VortexSession; pub fn initialize(session: &mut VortexSession) { session.arrays().register(RunEnd::ID, RunEnd); - // Register the RunEnd-specific min/max aggregate kernel. + // Register the RunEnd-specific aggregate kernels. session.aggregate_fns().register_aggregate_kernel( RunEnd::ID, Some(MinMax.id()), &compute::min_max::RunEndMinMaxKernel, ); + session.aggregate_fns().register_aggregate_kernel( + RunEnd::ID, + Some(IsConstant.id()), + &compute::is_constant::RunEndIsConstantKernel, + ); } #[cfg(test)] diff --git a/encodings/runend/src/ops.rs b/encodings/runend/src/ops.rs index c5b3ed48023..b26ee6978a7 100644 --- a/encodings/runend/src/ops.rs +++ b/encodings/runend/src/ops.rs @@ -44,11 +44,11 @@ mod tests { use vortex_array::DynArray; use vortex_array::IntoArray; + use vortex_array::LEGACY_SESSION; + use vortex_array::VortexSessionExecute; + use vortex_array::aggregate_fn::fns::is_constant::is_constant; use vortex_array::arrays::PrimitiveArray; use vortex_array::assert_arrays_eq; - use vortex_array::compute::Cost; - use vortex_array::compute::IsConstantOpts; - use vortex_array::compute::is_constant_opts; use vortex_array::dtype::DType; use vortex_array::dtype::Nullability; use vortex_array::dtype::PType; @@ -137,16 +137,8 @@ mod tests { let sliced_array = re_array.slice(2..5).unwrap(); - assert!( - is_constant_opts( - &sliced_array, - &IsConstantOpts { - cost: Cost::Canonicalize - } - ) - .unwrap() - .unwrap_or_default() - ) + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert!(is_constant(&sliced_array, &mut ctx).unwrap()) } #[test] diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/bool.rs b/vortex-array/src/aggregate_fn/fns/is_constant/bool.rs new file mode 100644 index 00000000000..9992cb6bbe8 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/is_constant/bool.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use crate::arrays::BoolArray; + +pub(super) fn check_bool_constant(array: &BoolArray) -> bool { + let true_count = array.to_bit_buffer().true_count(); + true_count == array.len() || true_count == 0 +} diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/decimal.rs b/vortex-array/src/aggregate_fn/fns/is_constant/decimal.rs new file mode 100644 index 00000000000..4a226f403f3 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/is_constant/decimal.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use itertools::Itertools; + +use crate::arrays::DecimalArray; +use crate::match_each_decimal_value_type; + +pub(super) fn check_decimal_constant(array: &DecimalArray) -> bool { + match_each_decimal_value_type!(array.values_type(), |S| { + array.buffer::().iter().all_equal() + }) +} diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs new file mode 100644 index 00000000000..d2b4156e3dd --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +mod bool; +mod decimal; +pub mod primitive; +mod varbin; + +use vortex_error::VortexResult; + +use self::bool::check_bool_constant; +use self::decimal::check_decimal_constant; +use self::primitive::check_primitive_constant; +use self::varbin::check_varbinview_constant; +use crate::ArrayRef; +use crate::Canonical; +use crate::Columnar; +use crate::DynArray; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::aggregate_fn::Accumulator; +use crate::aggregate_fn::AggregateFnId; +use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::DynAccumulator; +use crate::aggregate_fn::EmptyOptions; +use crate::arrays::Constant; +use crate::arrays::Null; +use crate::dtype::DType; +use crate::dtype::FieldNames; +use crate::dtype::Nullability; +use crate::dtype::StructFields; +use crate::expr::stats::Precision; +use crate::expr::stats::Stat; +use crate::expr::stats::StatsProvider; +use crate::expr::stats::StatsProviderExt; +use crate::scalar::Scalar; + +/// Compute whether an array has constant values. +/// +/// An array is constant IFF at least one of the following conditions apply: +/// 1. It has at least one element (**Note** - an empty array isn't constant). +/// 2. It's encoded as a [`ConstantArray`](crate::arrays::ConstantArray) or [`NullArray`](crate::arrays::NullArray) +/// 3. Has an exact statistic attached to it, saying its constant. +/// 4. Is all invalid. +/// 5. Is all valid AND has minimum and maximum statistics that are equal. +pub fn is_constant(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { + // Short-circuit using cached array statistics. + if let Some(Precision::Exact(value)) = array.statistics().get_as::(Stat::IsConstant) { + return Ok(value); + } + + // Empty arrays are not constant. + if array.is_empty() { + return Ok(false); + } + + // Array of length 1 is always constant. + if array.len() == 1 { + array + .statistics() + .set(Stat::IsConstant, Precision::Exact(true.into())); + return Ok(true); + } + + // Constant and null arrays are always constant. + if array.is::() || array.is::() { + array + .statistics() + .set(Stat::IsConstant, Precision::Exact(true.into())); + return Ok(true); + } + + let all_invalid = array.all_invalid()?; + if all_invalid { + array + .statistics() + .set(Stat::IsConstant, Precision::Exact(true.into())); + return Ok(true); + } + + let all_valid = array.all_valid()?; + + // If we have some nulls but not all nulls, array can't be constant. + if !all_valid && !all_invalid { + array + .statistics() + .set(Stat::IsConstant, Precision::Exact(false.into())); + return Ok(false); + } + + // We already know here that the array is all valid, so we check for min/max stats. + let min = array.statistics().get(Stat::Min); + let max = array.statistics().get(Stat::Max); + + if let Some((min, max)) = min.zip(max) + && min.is_exact() + && min == max + && (Stat::NaNCount.dtype(array.dtype()).is_none() + || array.statistics().get_as::(Stat::NaNCount) == Some(Precision::exact(0u64))) + { + array + .statistics() + .set(Stat::IsConstant, Precision::Exact(true.into())); + return Ok(true); + } + + // Short-circuit for unsupported dtypes. + if IsConstant + .return_dtype(&EmptyOptions, array.dtype()) + .is_none() + { + // Null dtype - vacuously false for empty + return Ok(false); + } + + // Compute using Accumulator. + let mut acc = Accumulator::try_new(IsConstant, EmptyOptions, array.dtype().clone())?; + acc.accumulate(array, ctx)?; + let result_scalar = acc.finish()?; + + let result = result_scalar.as_bool().value().unwrap_or(false); + + // Cache the computed is_constant as a statistic. + array + .statistics() + .set(Stat::IsConstant, Precision::Exact(result.into())); + + Ok(result) +} + +/// Compute whether an array is constant. +/// +/// Returns a `Bool(NonNullable)` scalar. +/// The partial state is a nullable struct `{is_constant: Bool(NN), value: input_dtype?}`. +/// A null struct means the accumulator has seen no data yet (empty). +#[derive(Clone, Debug)] +pub struct IsConstant; + +/// Partial accumulator state for is_constant. +pub struct IsConstantPartial { + is_constant: bool, + /// None = empty (no values seen), Some(null) = all nulls, Some(v) = first value seen. + first_value: Option, + element_dtype: DType, +} + +impl IsConstantPartial { + fn check_value(&mut self, value: Scalar) { + if !self.is_constant { + return; + } + match &self.first_value { + None => { + self.first_value = Some(value); + } + Some(first) => { + if *first != value { + self.is_constant = false; + } + } + } + } +} + +pub fn make_is_constant_partial_dtype(element_dtype: &DType) -> DType { + static NAMES: std::sync::LazyLock = + std::sync::LazyLock::new(|| FieldNames::from(["is_constant", "value"])); + DType::Struct( + StructFields::new( + NAMES.clone(), + vec![ + DType::Bool(Nullability::NonNullable), + element_dtype.as_nullable(), + ], + ), + Nullability::Nullable, + ) +} + +impl AggregateFnVTable for IsConstant { + type Options = EmptyOptions; + type Partial = IsConstantPartial; + + fn id(&self) -> AggregateFnId { + AggregateFnId::new_ref("vortex.is_constant") + } + + fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { + match input_dtype { + DType::Null => None, + _ => Some(DType::Bool(Nullability::NonNullable)), + } + } + + fn partial_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option { + match input_dtype { + DType::Null => None, + _ => Some(make_is_constant_partial_dtype(input_dtype)), + } + } + + fn empty_partial( + &self, + _options: &Self::Options, + input_dtype: &DType, + ) -> VortexResult { + Ok(IsConstantPartial { + is_constant: true, + first_value: None, + element_dtype: input_dtype.clone(), + }) + } + + fn combine_partials(&self, partial: &mut Self::Partial, other: Scalar) -> VortexResult<()> { + if !partial.is_constant { + return Ok(()); + } + + // Null struct means the other accumulator was empty, skip it. + if other.is_null() { + return Ok(()); + } + + let other_is_constant = other + .as_struct() + .field_by_idx(0) + .map(|s| s.as_bool().value().unwrap_or(false)) + .unwrap_or(false); + + if !other_is_constant { + partial.is_constant = false; + return Ok(()); + } + + let other_value = other.as_struct().field_by_idx(1); + + if let Some(other_val) = other_value { + partial.check_value(other_val); + } + + Ok(()) + } + + fn flush(&self, partial: &mut Self::Partial) -> VortexResult { + let dtype = make_is_constant_partial_dtype(&partial.element_dtype); + let result = match &partial.first_value { + None => { + // Empty accumulator — return null struct. + Scalar::null(dtype) + } + Some(first_value) => Scalar::struct_( + dtype, + vec![ + Scalar::bool(partial.is_constant, Nullability::NonNullable), + first_value + .clone() + .cast(&partial.element_dtype.as_nullable())?, + ], + ), + }; + + // Reset state. + partial.is_constant = true; + partial.first_value = None; + + Ok(result) + } + + #[inline] + fn is_saturated(&self, partial: &Self::Partial) -> bool { + !partial.is_constant + } + + fn accumulate( + &self, + partial: &mut Self::Partial, + batch: &Columnar, + _ctx: &mut ExecutionCtx, + ) -> VortexResult<()> { + if !partial.is_constant { + return Ok(()); + } + + match batch { + Columnar::Constant(c) => { + partial.check_value(c.scalar().clone().into_nullable()); + Ok(()) + } + Columnar::Canonical(c) => { + if c.is_empty() { + return Ok(()); + } + + // Convert to ArrayRef for DynArray methods. + let array_ref = c.clone().into_array(); + + let all_invalid = array_ref.all_invalid()?; + if all_invalid { + partial.check_value(Scalar::null(partial.element_dtype.as_nullable())); + return Ok(()); + } + + let all_valid = array_ref.all_valid()?; + // Mixed nulls → not constant. + if !all_valid && !all_invalid { + partial.is_constant = false; + return Ok(()); + } + + // All valid from here. Check batch-level constancy. + if c.len() == 1 { + partial.check_value(array_ref.scalar_at(0)?.into_nullable()); + return Ok(()); + } + + let batch_is_constant = match c { + Canonical::Primitive(p) => check_primitive_constant(p), + Canonical::Bool(b) => check_bool_constant(b), + Canonical::VarBinView(v) => check_varbinview_constant(v), + Canonical::Decimal(d) => check_decimal_constant(d), + Canonical::Struct(s) => { + let children = s.children(); + if children.is_empty() { + true + } else { + // For struct, check each child recursively. + let first_scalar = s.scalar_at(0)?; + let mut is_const = true; + for i in 1..s.len() { + if s.scalar_at(i)? != first_scalar { + is_const = false; + break; + } + } + is_const + } + } + Canonical::Extension(e) => { + // Extension arrays delegate to their storage. + let first_scalar = e.scalar_at(0)?; + let mut is_const = true; + for i in 1..e.len() { + if e.scalar_at(i)? != first_scalar { + is_const = false; + break; + } + } + is_const + } + Canonical::List(l) => { + let first_scalar = l.scalar_at(0)?; + let mut is_const = true; + for i in 1..l.len() { + if l.scalar_at(i)? != first_scalar { + is_const = false; + break; + } + } + is_const + } + Canonical::FixedSizeList(f) => { + let first_scalar = f.scalar_at(0)?; + let mut is_const = true; + for i in 1..f.len() { + if f.scalar_at(i)? != first_scalar { + is_const = false; + break; + } + } + is_const + } + Canonical::Null(_) => true, + }; + + if !batch_is_constant { + partial.is_constant = false; + return Ok(()); + } + + partial.check_value(array_ref.scalar_at(0)?.into_nullable()); + Ok(()) + } + } + } + + fn finalize(&self, partials: ArrayRef) -> VortexResult { + // TODO: extract is_constant field from struct array + Ok(partials) + } + + fn finalize_scalar(&self, partial: Scalar) -> VortexResult { + if partial.is_null() { + // Empty accumulator → return false. + return Ok(Scalar::bool(false, Nullability::NonNullable)); + } + let is_constant_val = partial + .as_struct() + .field_by_idx(0) + .map(|s| s.as_bool().value().unwrap_or(false)) + .unwrap_or(false); + Ok(Scalar::bool(is_constant_val, Nullability::NonNullable)) + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + use vortex_buffer::Buffer; + use vortex_buffer::buffer; + use vortex_error::VortexResult; + + use crate::IntoArray as _; + use crate::LEGACY_SESSION; + use crate::VortexSessionExecute; + use crate::aggregate_fn::fns::is_constant::is_constant; + use crate::arrays::BoolArray; + use crate::arrays::ChunkedArray; + use crate::arrays::DecimalArray; + use crate::arrays::ListArray; + use crate::arrays::PrimitiveArray; + use crate::arrays::StructArray; + use crate::dtype::DType; + use crate::dtype::DecimalDType; + use crate::dtype::FieldNames; + use crate::dtype::Nullability; + use crate::dtype::PType; + use crate::expr::stats::Stat; + use crate::validity::Validity; + + // Tests migrated from compute/is_constant.rs + #[test] + fn is_constant_min_max_no_nan() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + + let arr = buffer![0, 1].into_array(); + arr.statistics().compute_all(&[Stat::Min, Stat::Max])?; + assert!(!is_constant(&arr, &mut ctx)?); + + let arr = buffer![0, 0].into_array(); + arr.statistics().compute_all(&[Stat::Min, Stat::Max])?; + assert!(is_constant(&arr, &mut ctx)?); + + let arr = PrimitiveArray::from_option_iter([Some(0), Some(0)]).into_array(); + assert!(is_constant(&arr, &mut ctx)?); + Ok(()) + } + + #[test] + fn is_constant_min_max_with_nan() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + + let arr = PrimitiveArray::from_iter([0.0, 0.0, f32::NAN]).into_array(); + arr.statistics().compute_all(&[Stat::Min, Stat::Max])?; + assert!(!is_constant(&arr, &mut ctx)?); + + let arr = + PrimitiveArray::from_option_iter([Some(f32::NEG_INFINITY), Some(f32::NEG_INFINITY)]) + .into_array(); + arr.statistics().compute_all(&[Stat::Min, Stat::Max])?; + assert!(is_constant(&arr, &mut ctx)?); + Ok(()) + } + + // Tests migrated from arrays/bool/compute/is_constant.rs + #[rstest] + #[case(vec![true], true)] + #[case(vec![false; 65], true)] + #[case({ + let mut v = vec![true; 64]; + v.push(false); + v + }, false)] + fn test_bool_is_constant(#[case] input: Vec, #[case] expected: bool) -> VortexResult<()> { + let array = BoolArray::from_iter(input); + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert_eq!(is_constant(&array.into_array(), &mut ctx)?, expected); + Ok(()) + } + + // Tests migrated from arrays/chunked/compute/is_constant.rs + #[test] + fn empty_chunk_is_constant() -> VortexResult<()> { + let chunked = ChunkedArray::try_new( + vec![ + Buffer::::empty().into_array(), + Buffer::::empty().into_array(), + buffer![255u8, 255].into_array(), + Buffer::::empty().into_array(), + buffer![255u8, 255].into_array(), + ], + DType::Primitive(PType::U8, Nullability::NonNullable), + )? + .into_array(); + + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert!(is_constant(&chunked, &mut ctx)?); + Ok(()) + } + + // Tests migrated from arrays/decimal/compute/is_constant.rs + #[test] + fn test_decimal_is_constant() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + + let array = DecimalArray::new( + buffer![0i128, 1i128, 2i128], + DecimalDType::new(19, 0), + Validity::NonNullable, + ); + assert!(!is_constant(&array.into_array(), &mut ctx)?); + + let array = DecimalArray::new( + buffer![100i128, 100i128, 100i128], + DecimalDType::new(19, 0), + Validity::NonNullable, + ); + assert!(is_constant(&array.into_array(), &mut ctx)?); + Ok(()) + } + + // Tests migrated from arrays/list/compute/is_constant.rs + #[test] + fn test_is_constant_nested_list() -> VortexResult<()> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + + let xs = ListArray::try_new( + buffer![0i32, 1, 0, 1].into_array(), + buffer![0u32, 2, 4].into_array(), + Validity::NonNullable, + )?; + + let struct_of_lists = StructArray::try_new( + FieldNames::from(["xs"]), + vec![xs.into_array()], + 2, + Validity::NonNullable, + )?; + assert!(is_constant( + &struct_of_lists.clone().into_array(), + &mut ctx + )?); + assert!(is_constant(&struct_of_lists.into_array(), &mut ctx)?); + Ok(()) + } + + #[rstest] + #[case( + // [1,2], [1, 2], [1, 2] + vec![1i32, 2, 1, 2, 1, 2], + vec![0u32, 2, 4, 6], + true + )] + #[case( + // [1, 2], [3], [4, 5] + vec![1i32, 2, 3, 4, 5], + vec![0u32, 2, 3, 5], + false + )] + #[case( + // [1, 2], [3, 4] + vec![1i32, 2, 3, 4], + vec![0u32, 2, 4], + false + )] + #[case( + // [], [], [] + vec![], + vec![0u32, 0, 0, 0], + true + )] + fn test_list_is_constant( + #[case] elements: Vec, + #[case] offsets: Vec, + #[case] expected: bool, + ) -> VortexResult<()> { + let list_array = ListArray::try_new( + PrimitiveArray::from_iter(elements).into_array(), + PrimitiveArray::from_iter(offsets).into_array(), + Validity::NonNullable, + )?; + + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert_eq!(is_constant(&list_array.into_array(), &mut ctx)?, expected); + Ok(()) + } + + #[test] + fn test_list_is_constant_nested_lists() -> VortexResult<()> { + let inner_elements = buffer![1i32, 2, 1, 2].into_array(); + let inner_offsets = buffer![0u32, 1, 2, 3, 4].into_array(); + let inner_lists = ListArray::try_new(inner_elements, inner_offsets, Validity::NonNullable)?; + + let outer_offsets = buffer![0u32, 2, 4].into_array(); + let outer_list = ListArray::try_new( + inner_lists.into_array(), + outer_offsets, + Validity::NonNullable, + )?; + + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + // Both outer lists contain [[1], [2]], so should be constant + assert!(is_constant(&outer_list.into_array(), &mut ctx)?); + Ok(()) + } + + #[rstest] + #[case( + // 100 identical [1, 2] lists + [1i32, 2].repeat(100), + (0..101).map(|i| (i * 2) as u32).collect(), + true + )] + #[case( + // Difference after threshold: 64 identical [1, 2] + one [3, 4] + { + let mut elements = [1i32, 2].repeat(64); + elements.extend_from_slice(&[3, 4]); + elements + }, + (0..66).map(|i| (i * 2) as u32).collect(), + false + )] + #[case( + // Difference in first 64: first 63 identical [1, 2] + one [3, 4] + rest identical [1, 2] + { + let mut elements = [1i32, 2].repeat(63); + elements.extend_from_slice(&[3, 4]); + elements.extend([1i32, 2].repeat(37)); + elements + }, + (0..101).map(|i| (i * 2) as u32).collect(), + false + )] + fn test_list_is_constant_with_threshold( + #[case] elements: Vec, + #[case] offsets: Vec, + #[case] expected: bool, + ) -> VortexResult<()> { + let list_array = ListArray::try_new( + PrimitiveArray::from_iter(elements).into_array(), + PrimitiveArray::from_iter(offsets).into_array(), + Validity::NonNullable, + )?; + + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert_eq!(is_constant(&list_array.into_array(), &mut ctx)?, expected); + Ok(()) + } +} diff --git a/vortex-array/src/arrays/primitive/compute/is_constant.rs b/vortex-array/src/aggregate_fn/fns/is_constant/primitive.rs similarity index 58% rename from vortex-array/src/arrays/primitive/compute/is_constant.rs rename to vortex-array/src/aggregate_fn/fns/is_constant/primitive.rs index d9977d882eb..510306237ad 100644 --- a/vortex-array/src/arrays/primitive/compute/is_constant.rs +++ b/vortex-array/src/aggregate_fn/fns/is_constant/primitive.rs @@ -1,17 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -use vortex_error::VortexResult; - -use crate::arrays::Primitive; use crate::arrays::PrimitiveArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; use crate::dtype::NativePType; use crate::dtype::half::f16; use crate::match_each_native_ptype; -use crate::register_kernel; cfg_if::cfg_if! { if #[cfg(target_feature = "avx2")] { @@ -21,28 +14,6 @@ cfg_if::cfg_if! { } } -impl IsConstantKernel for Primitive { - fn is_constant( - &self, - array: &PrimitiveArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - if opts.is_negligible_cost() { - return Ok(None); - } - - let is_constant = match_each_native_ptype!(array.ptype(), integral: |P| { - compute_is_constant::<_, {IS_CONST_LANE_WIDTH / size_of::

()}>(array.as_slice::

()) - }, floating: |P| { - compute_is_constant::<_, {IS_CONST_LANE_WIDTH / size_of::

()}>(unsafe { std::mem::transmute::<&[P], &[

::IntType]>(array.as_slice::

()) }) - }); - - Ok(Some(is_constant)) - } -} - -register_kernel!(IsConstantKernelAdapter(Primitive).lift()); - /// Assumes any floating point has been cast into its bit representation for which != and !is_eq are the same /// Assumes there's at least 1 value in the slice, which is an invariant of the entry level function. pub fn compute_is_constant(values: &[T]) -> bool { @@ -79,3 +50,11 @@ impl EqFloat for f32 { impl EqFloat for f64 { type IntType = u64; } + +pub(super) fn check_primitive_constant(array: &PrimitiveArray) -> bool { + match_each_native_ptype!(array.ptype(), integral: |P| { + compute_is_constant::<_, {IS_CONST_LANE_WIDTH / size_of::

()}>(array.as_slice::

()) + }, floating: |P| { + compute_is_constant::<_, {IS_CONST_LANE_WIDTH / size_of::

()}>(unsafe { std::mem::transmute::<&[P], &[

::IntType]>(array.as_slice::

()) }) + }) +} diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/varbin.rs b/vortex-array/src/aggregate_fn/fns/is_constant/varbin.rs new file mode 100644 index 00000000000..aff91f0fffa --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/is_constant/varbin.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexExpect; + +use crate::arrays::VarBinViewArray; +use crate::arrays::varbinview::Ref; + +pub(super) fn check_varbinview_constant(array: &VarBinViewArray) -> bool { + let mut views_iter = array.views().iter(); + let first_value = views_iter + .next() + .vortex_expect("Must have at least one value"); + + if first_value.is_inlined() { + let first_value = first_value.as_inlined(); + + for view in views_iter { + if !view.is_inlined() || view.as_inlined() != first_value { + return false; + } + } + } else { + let ref_bytes = |view_ref: &Ref| { + &array.buffer(view_ref.buffer_index as usize).as_slice()[view_ref.as_range()] + }; + + let first_view_ref = first_value.as_view(); + let first_value_bytes = ref_bytes(first_view_ref); + + for view in views_iter { + if view.is_inlined() || view.len() != first_value.len() { + return false; + } + + let view_ref = view.as_view(); + let value = ref_bytes(view_ref); + if value != first_value_bytes { + return false; + } + } + } + + true +} diff --git a/vortex-array/src/aggregate_fn/fns/mod.rs b/vortex-array/src/aggregate_fn/fns/mod.rs index 638b29cc26c..b890375d124 100644 --- a/vortex-array/src/aggregate_fn/fns/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +pub mod is_constant; pub mod min_max; pub mod nan_count; pub mod sum; diff --git a/vortex-array/src/aggregate_fn/kernels.rs b/vortex-array/src/aggregate_fn/kernels.rs index ec659203c7a..d806b18d84d 100644 --- a/vortex-array/src/aggregate_fn/kernels.rs +++ b/vortex-array/src/aggregate_fn/kernels.rs @@ -17,8 +17,8 @@ use crate::scalar::Scalar; /// A pluggable kernel for an aggregate function. /// -/// The provided array should be aggregated into a single scalar representing the state of a single -/// group. +/// The provided array should be aggregated into a single scalar representing the partial state +/// of a single group. pub trait DynAggregateKernel: 'static + Send + Sync + Debug { fn aggregate( &self, diff --git a/vortex-array/src/aggregate_fn/session.rs b/vortex-array/src/aggregate_fn/session.rs index 1ccb68e1f4b..6d331492a7e 100644 --- a/vortex-array/src/aggregate_fn/session.rs +++ b/vortex-array/src/aggregate_fn/session.rs @@ -12,12 +12,14 @@ use vortex_utils::aliases::hash_map::HashMap; use crate::aggregate_fn::AggregateFnId; use crate::aggregate_fn::AggregateFnPluginRef; use crate::aggregate_fn::AggregateFnVTable; +use crate::aggregate_fn::fns::is_constant::IsConstant; use crate::aggregate_fn::fns::min_max::MinMax; use crate::aggregate_fn::kernels::DynAggregateKernel; use crate::aggregate_fn::kernels::DynGroupedAggregateKernel; use crate::arrays::Chunked; use crate::arrays::Dict; use crate::arrays::chunked::compute::aggregate::ChunkedArrayAggregate; +use crate::arrays::dict::compute::is_constant::DictIsConstantKernel; use crate::arrays::dict::compute::min_max::DictMinMaxKernel; use crate::vtable::ArrayId; @@ -46,6 +48,7 @@ impl Default for AggregateFnSession { // Register the built-in aggregate kernels. this.register_aggregate_kernel(Chunked::ID, None, &ChunkedArrayAggregate); this.register_aggregate_kernel(Dict::ID, Some(MinMax.id()), &DictMinMaxKernel); + this.register_aggregate_kernel(Dict::ID, Some(IsConstant.id()), &DictIsConstantKernel); this } diff --git a/vortex-array/src/arrays/bool/compute/is_constant.rs b/vortex-array/src/arrays/bool/compute/is_constant.rs deleted file mode 100644 index cdbef2dae08..00000000000 --- a/vortex-array/src/arrays/bool/compute/is_constant.rs +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexResult; - -use crate::arrays::Bool; -use crate::arrays::BoolArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::register_kernel; - -impl IsConstantKernel for Bool { - fn is_constant(&self, array: &BoolArray, opts: &IsConstantOpts) -> VortexResult> { - // If the array is small, then it is a constant time operation. - if opts.is_negligible_cost() && array.len() > 64 { - return Ok(None); - } - - let true_count = array.to_bit_buffer().true_count(); - Ok(Some(true_count == array.len() || true_count == 0)) - } -} - -register_kernel!(IsConstantKernelAdapter(Bool).lift()); - -#[cfg(test)] -mod tests { - use rstest::rstest; - - use super::*; - use crate::IntoArray; - use crate::compute::is_constant; - - #[rstest] - #[case(vec![true], true)] - #[case(vec![false; 65], true)] - #[case({ - let mut v = vec![true; 64]; - v.push(false); - v - }, false)] - fn test_is_constant(#[case] input: Vec, #[case] expected: bool) { - let array = BoolArray::from_iter(input); - assert_eq!(is_constant(&array.into_array()).unwrap(), Some(expected)); - } -} diff --git a/vortex-array/src/arrays/bool/compute/mod.rs b/vortex-array/src/arrays/bool/compute/mod.rs index 3faac2c028c..622430fb2ec 100644 --- a/vortex-array/src/arrays/bool/compute/mod.rs +++ b/vortex-array/src/arrays/bool/compute/mod.rs @@ -4,7 +4,6 @@ mod cast; mod fill_null; pub(crate) mod filter; -mod is_constant; mod is_sorted; mod mask; pub mod rules; diff --git a/vortex-array/src/arrays/chunked/compute/aggregate.rs b/vortex-array/src/arrays/chunked/compute/aggregate.rs index 86965f45072..e175754bc30 100644 --- a/vortex-array/src/arrays/chunked/compute/aggregate.rs +++ b/vortex-array/src/arrays/chunked/compute/aggregate.rs @@ -28,7 +28,9 @@ impl DynAggregateKernel for ChunkedArrayAggregate { for chunk in chunked.chunks() { acc.accumulate(chunk, ctx)?; } - Ok(Some(acc.finish()?)) + // Return the partial (not finalized) result, since the outer accumulator + // will call combine_partials() on this value. + Ok(Some(acc.flush()?)) } } diff --git a/vortex-array/src/arrays/chunked/compute/is_constant.rs b/vortex-array/src/arrays/chunked/compute/is_constant.rs deleted file mode 100644 index 8d294a27486..00000000000 --- a/vortex-array/src/arrays/chunked/compute/is_constant.rs +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexExpect; -use vortex_error::VortexResult; - -use crate::DynArray; -use crate::arrays::Chunked; -use crate::arrays::ChunkedArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::compute::is_constant_opts; -use crate::register_kernel; - -impl IsConstantKernel for Chunked { - fn is_constant( - &self, - array: &ChunkedArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - let mut chunks = array.non_empty_chunks(); - - let first_chunk = chunks - .next() - .vortex_expect("Must have at least one non-empty chunk"); - - match is_constant_opts(first_chunk, opts)? { - // Un-determined - None => return Ok(None), - Some(false) => return Ok(Some(false)), - Some(true) => {} - } - - let first_value = first_chunk.scalar_at(0)?.into_nullable(); - - for chunk in chunks { - match is_constant_opts(chunk, opts)? { - // Un-determined - None => return Ok(None), - Some(false) => return Ok(Some(false)), - Some(true) => {} - } - - if first_value != chunk.scalar_at(0)?.into_nullable() { - return Ok(Some(false)); - } - } - - Ok(Some(true)) - } -} - -register_kernel!(IsConstantKernelAdapter(Chunked).lift()); - -#[cfg(test)] -mod tests { - use vortex_buffer::Buffer; - use vortex_buffer::buffer; - - use crate::DynArray; - use crate::IntoArray; - use crate::arrays::ChunkedArray; - use crate::dtype::DType; - use crate::dtype::Nullability; - use crate::dtype::PType; - - #[test] - fn empty_chunk_is_constant() { - let chunked = ChunkedArray::try_new( - vec![ - Buffer::::empty().into_array(), - Buffer::::empty().into_array(), - buffer![255u8, 255].into_array(), - Buffer::::empty().into_array(), - buffer![255u8, 255].into_array(), - ], - DType::Primitive(PType::U8, Nullability::NonNullable), - ) - .unwrap() - .into_array(); - - assert!(chunked.statistics().compute_is_constant().unwrap()); - } -} diff --git a/vortex-array/src/arrays/chunked/compute/mod.rs b/vortex-array/src/arrays/chunked/compute/mod.rs index 334949caab7..8018a577516 100644 --- a/vortex-array/src/arrays/chunked/compute/mod.rs +++ b/vortex-array/src/arrays/chunked/compute/mod.rs @@ -5,7 +5,6 @@ pub(crate) mod aggregate; mod cast; mod fill_null; mod filter; -mod is_constant; mod is_sorted; pub(crate) mod kernel; mod mask; diff --git a/vortex-array/src/arrays/decimal/compute/is_constant.rs b/vortex-array/src/arrays/decimal/compute/is_constant.rs deleted file mode 100644 index 2208e054661..00000000000 --- a/vortex-array/src/arrays/decimal/compute/is_constant.rs +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use itertools::Itertools; -use vortex_error::VortexResult; - -use crate::arrays::Decimal; -use crate::arrays::DecimalArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::match_each_decimal_value_type; -use crate::register_kernel; - -impl IsConstantKernel for Decimal { - fn is_constant( - &self, - array: &DecimalArray, - _opts: &IsConstantOpts, - ) -> VortexResult> { - let constant = match_each_decimal_value_type!(array.values_type, |S| { - array.buffer::().iter().all_equal() - }); - Ok(Some(constant)) - } -} - -register_kernel!(IsConstantKernelAdapter(Decimal).lift()); - -#[cfg(test)] -mod tests { - use vortex_buffer::buffer; - - use crate::IntoArray; - use crate::arrays::DecimalArray; - use crate::compute::is_constant; - use crate::dtype::DecimalDType; - use crate::validity::Validity; - - #[test] - fn test_is_constant() { - let array = DecimalArray::new( - buffer![0i128, 1i128, 2i128], - DecimalDType::new(19, 0), - Validity::NonNullable, - ); - - assert!(!is_constant(&array.into_array()).unwrap().unwrap()); - - let array = DecimalArray::new( - buffer![100i128, 100i128, 100i128], - DecimalDType::new(19, 0), - Validity::NonNullable, - ); - - assert!(is_constant(&array.into_array()).unwrap().unwrap()); - } -} diff --git a/vortex-array/src/arrays/decimal/compute/mod.rs b/vortex-array/src/arrays/decimal/compute/mod.rs index 7b9c892738b..3b2d687b6bf 100644 --- a/vortex-array/src/arrays/decimal/compute/mod.rs +++ b/vortex-array/src/arrays/decimal/compute/mod.rs @@ -4,7 +4,6 @@ mod between; mod cast; mod fill_null; -mod is_constant; mod is_sorted; mod mask; pub mod rules; diff --git a/vortex-array/src/arrays/dict/compute/is_constant.rs b/vortex-array/src/arrays/dict/compute/is_constant.rs index 8113153032c..739986c5aba 100644 --- a/vortex-array/src/arrays/dict/compute/is_constant.rs +++ b/vortex-array/src/arrays/dict/compute/is_constant.rs @@ -3,22 +3,69 @@ use vortex_error::VortexResult; -use super::Dict; -use super::DictArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::compute::is_constant_opts; -use crate::register_kernel; +use crate::ArrayRef; +use crate::DynArray; +use crate::ExecutionCtx; +use crate::aggregate_fn::AggregateFnRef; +use crate::aggregate_fn::fns::is_constant::IsConstant; +use crate::aggregate_fn::fns::is_constant::is_constant; +use crate::aggregate_fn::kernels::DynAggregateKernel; +use crate::arrays::Dict; +use crate::scalar::Scalar; -impl IsConstantKernel for Dict { - fn is_constant(&self, array: &DictArray, opts: &IsConstantOpts) -> VortexResult> { - if is_constant_opts(array.codes(), opts)? == Some(true) { - return Ok(Some(true)); +/// Dict-specific is_constant kernel. +/// +/// If codes are constant, the whole array is constant. +/// Otherwise, check the values array. +#[derive(Debug)] +pub(crate) struct DictIsConstantKernel; + +impl DynAggregateKernel for DictIsConstantKernel { + fn aggregate( + &self, + aggregate_fn: &AggregateFnRef, + batch: &ArrayRef, + ctx: &mut ExecutionCtx, + ) -> VortexResult> { + if !aggregate_fn.is::() { + return Ok(None); } - is_constant_opts(array.values(), opts) + let Some(dict) = batch.as_opt::() else { + return Ok(None); + }; + + let result = if is_constant(dict.codes(), ctx)? { + true + } else { + is_constant(dict.values(), ctx)? + }; + + // Return in the partial dtype format: struct {is_constant, value} + // We use the first scalar as the representative value. + let partial_dtype = + crate::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype(batch.dtype()); + if result { + let first_value = if batch.is_empty() { + return Ok(Some(Scalar::null(partial_dtype))); + } else { + batch.scalar_at(0)?.into_nullable() + }; + Ok(Some(Scalar::struct_( + partial_dtype, + vec![ + Scalar::bool(true, crate::dtype::Nullability::NonNullable), + first_value, + ], + ))) + } else { + Ok(Some(Scalar::struct_( + partial_dtype, + vec![ + Scalar::bool(false, crate::dtype::Nullability::NonNullable), + Scalar::null(batch.dtype().as_nullable()), + ], + ))) + } } } - -register_kernel!(IsConstantKernelAdapter(Dict).lift()); diff --git a/vortex-array/src/arrays/dict/compute/mod.rs b/vortex-array/src/arrays/dict/compute/mod.rs index 0b1bb125bbd..3c2861a28f7 100644 --- a/vortex-array/src/arrays/dict/compute/mod.rs +++ b/vortex-array/src/arrays/dict/compute/mod.rs @@ -4,7 +4,7 @@ mod cast; mod compare; mod fill_null; -mod is_constant; +pub(crate) mod is_constant; mod is_sorted; mod like; mod mask; diff --git a/vortex-array/src/arrays/extension/compute/is_constant.rs b/vortex-array/src/arrays/extension/compute/is_constant.rs deleted file mode 100644 index dab838926e4..00000000000 --- a/vortex-array/src/arrays/extension/compute/is_constant.rs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexResult; - -use crate::arrays::Extension; -use crate::arrays::ExtensionArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::compute::{self}; -use crate::register_kernel; - -impl IsConstantKernel for Extension { - fn is_constant( - &self, - array: &ExtensionArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - compute::is_constant_opts(array.storage_array(), opts) - } -} - -register_kernel!(IsConstantKernelAdapter(Extension).lift()); diff --git a/vortex-array/src/arrays/extension/compute/mod.rs b/vortex-array/src/arrays/extension/compute/mod.rs index 614d10f69c2..7020ceb66a3 100644 --- a/vortex-array/src/arrays/extension/compute/mod.rs +++ b/vortex-array/src/arrays/extension/compute/mod.rs @@ -4,7 +4,6 @@ mod cast; mod compare; mod filter; -mod is_constant; mod is_sorted; mod mask; pub(crate) mod rules; diff --git a/vortex-array/src/arrays/fixed_size_list/compute/is_constant.rs b/vortex-array/src/arrays/fixed_size_list/compute/is_constant.rs deleted file mode 100644 index 7f3dd67c4a7..00000000000 --- a/vortex-array/src/arrays/fixed_size_list/compute/is_constant.rs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexResult; - -use crate::arrays::FixedSizeList; -use crate::arrays::FixedSizeListArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::register_kernel; - -/// IsConstant implementation for [`FixedSizeListArray`]. -/// -/// Compares each list scalar against the first to determine if all lists are identical. -impl IsConstantKernel for FixedSizeList { - fn is_constant( - &self, - array: &FixedSizeListArray, - _opts: &IsConstantOpts, - ) -> VortexResult> { - // Since all of the lists have fixed size, we just need to check that each list scalar is - // identical. Note that this check is always "expensive". - - debug_assert!( - array.len() > 1, - "precondition for `is_constant` is incorrect" - ); - let first_scalar = array.scalar_at(0)?; // We checked the array length above. - - // TODO(connor): There must be a more efficient way to do this. Each `scalar_at()` call - // makes several allocations... - for i in 1..array.len() { - let current_scalar = array.scalar_at(i)?; - if current_scalar != first_scalar { - return Ok(Some(false)); - } - } - - Ok(Some(true)) - } -} - -register_kernel!(IsConstantKernelAdapter(FixedSizeList).lift()); diff --git a/vortex-array/src/arrays/fixed_size_list/compute/mod.rs b/vortex-array/src/arrays/fixed_size_list/compute/mod.rs index 637b151ed88..72d2f8d64c3 100644 --- a/vortex-array/src/arrays/fixed_size_list/compute/mod.rs +++ b/vortex-array/src/arrays/fixed_size_list/compute/mod.rs @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors mod cast; -mod is_constant; mod is_sorted; mod mask; pub(crate) mod rules; diff --git a/vortex-array/src/arrays/list/compute/is_constant.rs b/vortex-array/src/arrays/list/compute/is_constant.rs deleted file mode 100644 index e08c73ad004..00000000000 --- a/vortex-array/src/arrays/list/compute/is_constant.rs +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexResult; - -use crate::arrays::List; -use crate::arrays::ListArray; -use crate::builtins::ArrayBuiltins; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::compute::is_constant; -use crate::register_kernel; -use crate::scalar_fn::fns::operators::Operator; - -const SMALL_ARRAY_THRESHOLD: usize = 64; - -impl IsConstantKernel for List { - fn is_constant(&self, array: &ListArray, opts: &IsConstantOpts) -> VortexResult> { - // At this point, we're guaranteed: - // - Array has at least 2 elements - // - All elements are valid (no nulls) - - let manual_check_until = std::cmp::min(SMALL_ARRAY_THRESHOLD, array.len()); - - // We can first quickly check if all of the list lengths are equal. If not, then we know the - // array cannot be constant. - let first_list_len = array.offset_at(1)? - array.offset_at(0)?; - for i in 1..manual_check_until { - let current_list_len = array.offset_at(i + 1)? - array.offset_at(i)?; - if current_list_len != first_list_len { - return Ok(Some(false)); - } - } - - // Since we were not able to determine that this list array was **not** constant, and the - // cost is negligible, then don't bother doing the rest of this expensive check. - if opts.is_negligible_cost() { - return Ok(None); - } - - // If the array is long, do an optimistic check on the remainder of the list lengths. - if array.len() > SMALL_ARRAY_THRESHOLD { - // check the rest of the element lengths - let start_offsets = array.offsets().slice(SMALL_ARRAY_THRESHOLD..array.len())?; - let end_offsets = array - .offsets() - .slice(SMALL_ARRAY_THRESHOLD + 1..array.len() + 1)?; - let list_lengths = end_offsets.binary(start_offsets, Operator::Sub)?; - - if !is_constant(&list_lengths)?.unwrap_or_default() { - return Ok(Some(false)); - } - } - - debug_assert!( - array.len() > 1, - "precondition for `is_constant` is incorrect" - ); - let first_scalar = array.scalar_at(0)?; // We checked the array length above. - - // All lists have the same length, so compare the actual list contents. - for i in 1..array.len() { - let current_scalar = array.scalar_at(i)?; - if current_scalar != first_scalar { - return Ok(Some(false)); - } - } - - Ok(Some(true)) - } -} - -register_kernel!(IsConstantKernelAdapter(List).lift()); - -#[cfg(test)] -mod tests { - - use rstest::rstest; - use vortex_buffer::buffer; - - use crate::IntoArray; - use crate::arrays::ListArray; - use crate::arrays::PrimitiveArray; - use crate::arrays::StructArray; - use crate::compute::is_constant; - use crate::dtype::FieldNames; - use crate::validity::Validity; - - #[test] - fn test_is_constant_nested_list() { - let xs = ListArray::try_new( - buffer![0i32, 1, 0, 1].into_array(), - buffer![0u32, 2, 4].into_array(), - Validity::NonNullable, - ) - .unwrap(); - - let struct_of_lists = StructArray::try_new( - FieldNames::from(["xs"]), - vec![xs.into_array()], - 2, - Validity::NonNullable, - ) - .unwrap(); - assert!( - is_constant(&struct_of_lists.clone().into_array()) - .unwrap() - .unwrap() - ); - assert!( - is_constant(&struct_of_lists.into_array()) - .unwrap() - .unwrap_or_default() - ); - } - - #[rstest] - #[case( - // [1,2], [1, 2], [1, 2] - vec![1i32, 2, 1, 2, 1, 2], - vec![0u32, 2, 4, 6], - true - )] - #[case( - // [1, 2], [3], [4, 5] - vec![1i32, 2, 3, 4, 5], - vec![0u32, 2, 3, 5], - false - )] - #[case( - // [1, 2], [3, 4] - vec![1i32, 2, 3, 4], - vec![0u32, 2, 4], - false - )] - #[case( - // [], [], [] - vec![], - vec![0u32, 0, 0, 0], - true - )] - fn test_list_is_constant( - #[case] elements: Vec, - #[case] offsets: Vec, - #[case] expected: bool, - ) { - let list_array = ListArray::try_new( - PrimitiveArray::from_iter(elements).into_array(), - PrimitiveArray::from_iter(offsets).into_array(), - Validity::NonNullable, - ) - .unwrap(); - - let result = is_constant(&list_array.into_array()).unwrap(); - assert_eq!(result.unwrap(), expected); - } - - #[test] - fn test_list_is_constant_nested_lists() { - let inner_elements = buffer![1i32, 2, 1, 2].into_array(); - let inner_offsets = buffer![0u32, 1, 2, 3, 4].into_array(); - let inner_lists = - ListArray::try_new(inner_elements, inner_offsets, Validity::NonNullable).unwrap(); - - let outer_offsets = buffer![0u32, 2, 4].into_array(); - let outer_list = ListArray::try_new( - inner_lists.into_array(), - outer_offsets, - Validity::NonNullable, - ) - .unwrap(); - - // Both outer lists contain [[1], [2]], so should be constant - assert!(is_constant(&outer_list.into_array()).unwrap().unwrap()); - } - - #[rstest] - #[case( - // 100 identical [1, 2] lists - [1i32, 2].repeat(100), - (0..101).map(|i| (i * 2) as u32).collect(), - true - )] - #[case( - // Difference after threshold: 64 identical [1, 2] + one [3, 4] - { - let mut elements = [1i32, 2].repeat(64); - elements.extend_from_slice(&[3, 4]); - elements - }, - (0..66).map(|i| (i * 2) as u32).collect(), - false - )] - #[case( - // Difference in first 64: first 63 identical [1, 2] + one [3, 4] + rest identical [1, 2] - { - let mut elements = [1i32, 2].repeat(63); - elements.extend_from_slice(&[3, 4]); - elements.extend([1i32, 2].repeat(37)); - elements - }, - (0..101).map(|i| (i * 2) as u32).collect(), - false - )] - fn test_list_is_constant_with_threshold( - #[case] elements: Vec, - #[case] offsets: Vec, - #[case] expected: bool, - ) { - let list_array = ListArray::try_new( - PrimitiveArray::from_iter(elements).into_array(), - PrimitiveArray::from_iter(offsets).into_array(), - Validity::NonNullable, - ) - .unwrap(); - - let result = is_constant(&list_array.into_array()).unwrap(); - assert_eq!(result.unwrap(), expected); - } -} diff --git a/vortex-array/src/arrays/list/compute/mod.rs b/vortex-array/src/arrays/list/compute/mod.rs index 72649fb118a..255ae96b744 100644 --- a/vortex-array/src/arrays/list/compute/mod.rs +++ b/vortex-array/src/arrays/list/compute/mod.rs @@ -3,7 +3,6 @@ mod cast; mod filter; -mod is_constant; mod is_sorted; mod kernels; mod mask; diff --git a/vortex-array/src/arrays/listview/compute/is_constant.rs b/vortex-array/src/arrays/listview/compute/is_constant.rs deleted file mode 100644 index abb64fc3a21..00000000000 --- a/vortex-array/src/arrays/listview/compute/is_constant.rs +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexResult; - -use crate::arrays::ListView; -use crate::arrays::ListViewArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::compute::is_constant_opts; -use crate::register_kernel; - -impl IsConstantKernel for ListView { - fn is_constant( - &self, - array: &ListViewArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - // At this point, we're guaranteed: - // - Array has at least 2 elements - // - All elements are valid (no nulls) - - // First check if all list sizes are constant. - if !is_constant_opts(array.sizes(), opts)?.unwrap_or_default() { - return Ok(Some(false)); - } - - // If checking is too expensive, return None early. - if opts.is_negligible_cost() { - return Ok(None); - } - - // Get the first scalar for comparison. - debug_assert!( - array.len() > 1, - "precondition for `is_constant` is incorrect" - ); - let first_scalar = array.scalar_at(0)?; - - // Compare all other scalars to the first. - for i in 1..array.len() { - if array.scalar_at(i)? != first_scalar { - return Ok(Some(false)); - } - } - - Ok(Some(true)) - } -} - -register_kernel!(IsConstantKernelAdapter(ListView).lift()); diff --git a/vortex-array/src/arrays/listview/compute/mod.rs b/vortex-array/src/arrays/listview/compute/mod.rs index 637b151ed88..72d2f8d64c3 100644 --- a/vortex-array/src/arrays/listview/compute/mod.rs +++ b/vortex-array/src/arrays/listview/compute/mod.rs @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors mod cast; -mod is_constant; mod is_sorted; mod mask; pub(crate) mod rules; diff --git a/vortex-array/src/arrays/listview/tests/operations.rs b/vortex-array/src/arrays/listview/tests/operations.rs index c680063cf5d..c22a21954d5 100644 --- a/vortex-array/src/arrays/listview/tests/operations.rs +++ b/vortex-array/src/arrays/listview/tests/operations.rs @@ -12,7 +12,10 @@ use super::common::create_large_listview; use super::common::create_nullable_listview; use crate::DynArray; use crate::IntoArray; +use crate::LEGACY_SESSION; use crate::ToCanonical; +use crate::VortexSessionExecute; +use crate::aggregate_fn::fns::is_constant::is_constant; use crate::arrays::BoolArray; use crate::arrays::ConstantArray; use crate::arrays::ListView; @@ -21,7 +24,6 @@ use crate::arrays::PrimitiveArray; use crate::assert_arrays_eq; use crate::builtins::ArrayBuiltins; use crate::compute::conformance::mask::test_mask_conformance; -use crate::compute::is_constant; use crate::dtype::DType; use crate::dtype::Nullability; use crate::dtype::PType; @@ -408,7 +410,8 @@ fn test_is_constant_basic( ) .into_array(); - assert_eq!(is_constant(&listview).unwrap(), Some(expected)); + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert_eq!(is_constant(&listview, &mut ctx).unwrap(), expected); } #[test] @@ -426,7 +429,8 @@ fn test_constant_with_constant_elements() { .into_array(); // All lists contain [42, 42] so should be constant. - assert_eq!(is_constant(&listview).unwrap(), Some(true)); + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert!(is_constant(&listview, &mut ctx).unwrap()); } #[test] @@ -449,7 +453,8 @@ fn test_constant_with_nulls() { .with_zero_copy_to_list(true) } .into_array(); - assert_eq!(is_constant(&listview_mixed).unwrap(), Some(false)); + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert!(!is_constant(&listview_mixed, &mut ctx).unwrap()); // Case 2: All nulls - should be constant. let validity_all_null = Validity::AllInvalid; @@ -463,7 +468,8 @@ fn test_constant_with_nulls() { .with_zero_copy_to_list(true) } .into_array(); - assert_eq!(is_constant(&listview_all_null).unwrap(), Some(true)); + let mut ctx2 = LEGACY_SESSION.create_execution_ctx(); + assert!(is_constant(&listview_all_null, &mut ctx2).unwrap()); } #[test] @@ -477,7 +483,8 @@ fn test_constant_repeated_same_lists() { let listview = ListViewArray::new(elements, offsets, sizes, Validity::NonNullable).into_array(); // All lists are [10, 20, 30] so should be constant. - assert_eq!(is_constant(&listview).unwrap(), Some(true)); + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + assert!(is_constant(&listview, &mut ctx).unwrap()); } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/vortex-array/src/arrays/primitive/compute/mod.rs b/vortex-array/src/arrays/primitive/compute/mod.rs index 07c611bb85a..e6a1542acde 100644 --- a/vortex-array/src/arrays/primitive/compute/mod.rs +++ b/vortex-array/src/arrays/primitive/compute/mod.rs @@ -4,15 +4,12 @@ mod between; mod cast; mod fill_null; -mod is_constant; mod is_sorted; mod mask; pub(crate) mod rules; mod slice; mod take; -pub use is_constant::*; - #[cfg(test)] mod tests { use rstest::rstest; diff --git a/vortex-array/src/arrays/primitive/mod.rs b/vortex-array/src/arrays/primitive/mod.rs index abac02d949f..14092475a18 100644 --- a/vortex-array/src/arrays/primitive/mod.rs +++ b/vortex-array/src/arrays/primitive/mod.rs @@ -8,8 +8,6 @@ pub use array::chunk_range; pub use array::patch_chunk; pub(crate) mod compute; -pub use compute::IS_CONST_LANE_WIDTH; -pub use compute::compute_is_constant; mod vtable; pub use compute::rules::PrimitiveMaskedValidityRule; diff --git a/vortex-array/src/arrays/struct_/compute/is_constant.rs b/vortex-array/src/arrays/struct_/compute/is_constant.rs deleted file mode 100644 index 33b1cae9a17..00000000000 --- a/vortex-array/src/arrays/struct_/compute/is_constant.rs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexResult; - -use crate::arrays::Struct; -use crate::arrays::StructArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::compute::{self}; -use crate::register_kernel; - -impl IsConstantKernel for Struct { - fn is_constant( - &self, - array: &StructArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - let children = array.children(); - if children.is_empty() { - return Ok(Some(true)); - } - - for child in children.iter() { - match compute::is_constant_opts(child, opts)? { - // Un-determined - None => return Ok(None), - Some(false) => return Ok(Some(false)), - Some(true) => {} - } - } - - Ok(Some(true)) - } -} - -register_kernel!(IsConstantKernelAdapter(Struct).lift()); diff --git a/vortex-array/src/arrays/struct_/compute/mod.rs b/vortex-array/src/arrays/struct_/compute/mod.rs index bdf5d87d5f3..af6d73cca69 100644 --- a/vortex-array/src/arrays/struct_/compute/mod.rs +++ b/vortex-array/src/arrays/struct_/compute/mod.rs @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors mod cast; -mod is_constant; mod mask; pub(crate) mod rules; mod slice; @@ -23,6 +22,7 @@ mod tests { use crate::IntoArray as _; use crate::LEGACY_SESSION; use crate::VortexSessionExecute; + use crate::aggregate_fn::fns::is_constant::is_constant; use crate::arrays::BoolArray; use crate::arrays::PrimitiveArray; use crate::arrays::StructArray; @@ -32,7 +32,6 @@ mod tests { use crate::compute::conformance::consistency::test_array_consistency; use crate::compute::conformance::mask::test_mask_conformance; use crate::compute::conformance::take::test_take_conformance; - use crate::compute::is_constant; use crate::dtype::DType; use crate::dtype::FieldNames; use crate::dtype::Nullability; @@ -251,9 +250,10 @@ mod tests { #[test] fn test_empty_struct_is_constant() { let array = StructArray::new_fieldless_with_len(2); - let is_constant = - is_constant(&array.into_array()).vortex_expect("operation should succeed in test"); - assert_eq!(is_constant, Some(true)); + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + let result = is_constant(&array.into_array(), &mut ctx) + .vortex_expect("operation should succeed in test"); + assert!(result); } #[test] diff --git a/vortex-array/src/arrays/varbin/compute/is_constant.rs b/vortex-array/src/arrays/varbin/compute/is_constant.rs deleted file mode 100644 index a0860cfdec0..00000000000 --- a/vortex-array/src/arrays/varbin/compute/is_constant.rs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexResult; - -use crate::accessor::ArrayAccessor; -use crate::arrays::VarBin; -use crate::arrays::VarBinArray; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::register_kernel; - -impl IsConstantKernel for VarBin { - fn is_constant( - &self, - array: &VarBinArray, - opts: &IsConstantOpts, - ) -> VortexResult> { - if opts.is_negligible_cost() { - return Ok(None); - } - Ok(Some(array.with_iterator(compute_is_constant))) - } -} - -register_kernel!(IsConstantKernelAdapter(VarBin).lift()); - -pub(super) fn compute_is_constant(iter: &mut dyn Iterator>) -> bool { - let Some(first_value) = iter.next() else { - return false; - }; - for v in iter { - if v != first_value { - return false; - } - } - true -} diff --git a/vortex-array/src/arrays/varbin/compute/mod.rs b/vortex-array/src/arrays/varbin/compute/mod.rs index be6afde05a8..0abd02252be 100644 --- a/vortex-array/src/arrays/varbin/compute/mod.rs +++ b/vortex-array/src/arrays/varbin/compute/mod.rs @@ -7,7 +7,6 @@ mod slice; mod cast; mod compare; mod filter; -mod is_constant; mod is_sorted; mod mask; mod take; diff --git a/vortex-array/src/arrays/varbinview/compute/is_constant.rs b/vortex-array/src/arrays/varbinview/compute/is_constant.rs deleted file mode 100644 index 96f255ca93d..00000000000 --- a/vortex-array/src/arrays/varbinview/compute/is_constant.rs +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -use vortex_error::VortexExpect; -use vortex_error::VortexResult; - -use crate::arrays::VarBinView; -use crate::arrays::VarBinViewArray; -use crate::arrays::varbinview::Ref; -use crate::compute::IsConstantKernel; -use crate::compute::IsConstantKernelAdapter; -use crate::compute::IsConstantOpts; -use crate::register_kernel; - -impl IsConstantKernel for VarBinView { - fn is_constant( - &self, - array: &VarBinViewArray, - _opts: &IsConstantOpts, - ) -> VortexResult> { - let mut views_iter = array.views().iter(); - let first_value = views_iter - .next() - .vortex_expect("Must have at least one value"); - - // For the array to be constant, all views must be of the same type - if first_value.is_inlined() { - let first_value = first_value.as_inlined(); - - for view in views_iter { - // Short circuit if the view is of the wrong type, then if both are inlined they must be equal. - if !view.is_inlined() || view.as_inlined() != first_value { - return Ok(Some(false)); - } - } - } else { - // Directly fetch the values for a `Ref` - let ref_bytes = |view_ref: &Ref| { - &array.buffer(view_ref.buffer_index as usize).as_slice()[view_ref.as_range()] - }; - - let first_view_ref = first_value.as_view(); - let first_value_bytes = ref_bytes(first_view_ref); - - for view in views_iter { - // Short circuit if the view is of the wrong type - if view.is_inlined() || view.len() != first_value.len() { - return Ok(Some(false)); - } - - let view_ref = view.as_view(); - let value = ref_bytes(view_ref); - if value != first_value_bytes { - return Ok(Some(false)); - } - } - } - - Ok(Some(true)) - } -} - -register_kernel!(IsConstantKernelAdapter(VarBinView).lift()); diff --git a/vortex-array/src/arrays/varbinview/compute/mod.rs b/vortex-array/src/arrays/varbinview/compute/mod.rs index c266f779444..5fe39e3d309 100644 --- a/vortex-array/src/arrays/varbinview/compute/mod.rs +++ b/vortex-array/src/arrays/varbinview/compute/mod.rs @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors mod cast; -mod is_constant; mod is_sorted; mod mask; pub(crate) mod rules; diff --git a/vortex-array/src/compute/is_constant.rs b/vortex-array/src/compute/is_constant.rs index a0563704089..1666c4bad2b 100644 --- a/vortex-array/src/compute/is_constant.rs +++ b/vortex-array/src/compute/is_constant.rs @@ -2,263 +2,33 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors use std::any::Any; -use std::sync::LazyLock; -use arcref::ArcRef; -use vortex_error::VortexError; use vortex_error::VortexResult; -use vortex_error::vortex_bail; -use vortex_error::vortex_err; use crate::ArrayRef; -use crate::DynArray; -use crate::IntoArray as _; -use crate::arrays::Constant; -use crate::arrays::Null; -use crate::compute::ComputeFn; -use crate::compute::ComputeFnVTable; -use crate::compute::InvocationArgs; -use crate::compute::Kernel; -use crate::compute::Options; -use crate::compute::Output; -use crate::dtype::DType; -use crate::dtype::Nullability; -use crate::expr::stats::Precision; -use crate::expr::stats::Stat; -use crate::expr::stats::StatsProvider; -use crate::expr::stats::StatsProviderExt; -use crate::scalar::Scalar; -use crate::vtable::VTable; +use crate::LEGACY_SESSION; +use crate::VortexSessionExecute; -static IS_CONSTANT_FN: LazyLock = LazyLock::new(|| { - let compute = ComputeFn::new("is_constant".into(), ArcRef::new_ref(&IsConstant)); - for kernel in inventory::iter:: { - compute.register_kernel(kernel.0.clone()); - } - compute -}); - -pub(crate) fn warm_up_vtable() -> usize { - IS_CONSTANT_FN.kernels().len() -} - -/// Computes whether an array has constant values. If the array's encoding doesn't implement the -/// relevant VTable, it'll try and canonicalize in order to make a determination. -/// -/// An array is constant IFF at least one of the following conditions apply: -/// 1. It has at least one element (**Note** - an empty array isn't constant). -/// 1. It's encoded as a [`crate::arrays::ConstantArray`] or [`crate::arrays::NullArray`] -/// 1. Has an exact statistic attached to it, saying its constant. -/// 1. Is all invalid. -/// 1. Is all valid AND has minimum and maximum statistics that are equal. +/// Computes whether an array has constant values. /// -/// If the array has some null values but is not all null, it'll never be constant. -/// -/// Returns `Ok(None)` if we could not determine whether the array is constant, e.g. if -/// canonicalization is disabled and the no kernel exists for the array's encoding. +/// **Deprecated**: Use [`crate::aggregate_fn::fns::is_constant::is_constant`] instead. +#[deprecated(note = "Use crate::aggregate_fn::fns::is_constant::is_constant instead")] pub fn is_constant(array: &ArrayRef) -> VortexResult> { - let opts = IsConstantOpts::default(); - is_constant_opts(array, &opts) + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + Ok(Some(crate::aggregate_fn::fns::is_constant::is_constant( + array, &mut ctx, + )?)) } -/// Computes whether an array has constant values. Configurable by [`IsConstantOpts`]. +/// Computes whether an array has constant values. /// -/// Please see [`is_constant`] for a more detailed explanation of its behavior. -pub fn is_constant_opts(array: &ArrayRef, options: &IsConstantOpts) -> VortexResult> { - Ok(IS_CONSTANT_FN - .invoke(&InvocationArgs { - inputs: &[array.into()], - options, - })? - .unwrap_scalar()? - .as_bool() - .value()) -} - -struct IsConstant; - -impl ComputeFnVTable for IsConstant { - fn invoke( - &self, - args: &InvocationArgs, - kernels: &[ArcRef], - ) -> VortexResult { - let IsConstantArgs { array, options } = IsConstantArgs::try_from(args)?; - let array = array.to_array(); - - // We try and rely on some easy-to-get stats - if let Some(Precision::Exact(value)) = array.statistics().get_as::(Stat::IsConstant) { - let scalar: Scalar = Some(value).into(); - return Ok(scalar.into()); - } - - let value = is_constant_impl(&array, options, kernels)?; - - if options.cost == Cost::Canonicalize { - // When we run linear canonicalize, there we must always return an exact answer. - assert!( - value.is_some(), - "is constant in array {array} canonicalize returned None" - ); - } - - // Only if we made a determination do we update the stats. - if let Some(value) = value { - array - .statistics() - .set(Stat::IsConstant, Precision::Exact(value.into())); - } - - let scalar: Scalar = value.into(); - Ok(scalar.into()) - } - - fn return_dtype(&self, _args: &InvocationArgs) -> VortexResult { - // We always return a nullable boolean where `null` indicates we couldn't determine - // whether the array is constant. - Ok(DType::Bool(Nullability::Nullable)) - } - - fn return_len(&self, _args: &InvocationArgs) -> VortexResult { - Ok(1) - } - - fn is_elementwise(&self) -> bool { - false - } -} - -fn is_constant_impl( - array: &ArrayRef, - options: &IsConstantOpts, - kernels: &[ArcRef], -) -> VortexResult> { - match array.len() { - // Our current semantics are that we can always get a value out of a constant array. We might want to change that in the future. - 0 => return Ok(Some(false)), - // Array of length 1 is always constant. - 1 => return Ok(Some(true)), - _ => {} - } - - // Constant and null arrays are always constant - if array.is::() || array.is::() { - return Ok(Some(true)); - } - - let all_invalid = array.all_invalid()?; - if all_invalid { - return Ok(Some(true)); - } - - let all_valid = array.all_valid()?; - - // If we have some nulls, array can't be constant - if !all_valid && !all_invalid { - return Ok(Some(false)); - } - - // We already know here that the array is all valid, so we check for min/max stats. - let min = array.statistics().get(Stat::Min); - let max = array.statistics().get(Stat::Max); - - if let Some((min, max)) = min.zip(max) { - // min/max are equal and exact and there are no NaNs - if min.is_exact() - && min == max - && (Stat::NaNCount.dtype(array.dtype()).is_none() - || array.statistics().get_as::(Stat::NaNCount) == Some(Precision::exact(0u64))) - { - return Ok(Some(true)); - } - } - - assert!( - all_valid, - "All values must be valid as an invariant of the VTable." - ); - let args = InvocationArgs { - inputs: &[array.into()], - options, - }; - for kernel in kernels { - if let Some(output) = kernel.invoke(&args)? { - return Ok(output.unwrap_scalar()?.as_bool().value()); - } - } - - tracing::debug!( - "No is_constant implementation found for {}", - array.encoding_id() - ); - - if options.cost == Cost::Canonicalize && !array.is_canonical() { - let array = array.to_canonical()?.into_array(); - let is_constant = is_constant_opts(&array, options)?; - return Ok(is_constant); - } - - // Otherwise, we cannot determine if the array is constant. - Ok(None) -} - -pub struct IsConstantKernelRef(ArcRef); -inventory::collect!(IsConstantKernelRef); - -pub trait IsConstantKernel: VTable { - /// # Preconditions - /// - /// * All values are valid - /// * array.len() > 1 - /// - /// Returns `Ok(None)` to signal we couldn't make an exact determination. - fn is_constant(&self, array: &Self::Array, opts: &IsConstantOpts) - -> VortexResult>; -} - -#[derive(Debug)] -pub struct IsConstantKernelAdapter(pub V); - -impl IsConstantKernelAdapter { - pub const fn lift(&'static self) -> IsConstantKernelRef { - IsConstantKernelRef(ArcRef::new_ref(self)) - } -} - -impl Kernel for IsConstantKernelAdapter { - fn invoke(&self, args: &InvocationArgs) -> VortexResult> { - let args = IsConstantArgs::try_from(args)?; - let Some(array) = args.array.as_opt::() else { - return Ok(None); - }; - let is_constant = V::is_constant(&self.0, array, args.options)?; - let scalar: Scalar = is_constant.into(); - Ok(Some(scalar.into())) - } -} - -struct IsConstantArgs<'a> { - array: &'a dyn DynArray, - options: &'a IsConstantOpts, -} - -impl<'a> TryFrom<&InvocationArgs<'a>> for IsConstantArgs<'a> { - type Error = VortexError; - - fn try_from(value: &InvocationArgs<'a>) -> Result { - if value.inputs.len() != 1 { - vortex_bail!("Expected 1 input, found {}", value.inputs.len()); - } - let array = value.inputs[0] - .array() - .ok_or_else(|| vortex_err!("Expected input 0 to be an array"))?; - let options = value - .options - .as_any() - .downcast_ref::() - .ok_or_else(|| vortex_err!("Expected options to be of type IsConstantOpts"))?; - Ok(Self { array, options }) - } +/// **Deprecated**: Use [`crate::aggregate_fn::fns::is_constant::is_constant`] instead. +#[deprecated(note = "Use crate::aggregate_fn::fns::is_constant::is_constant instead")] +pub fn is_constant_opts(array: &ArrayRef, _opts: &IsConstantOpts) -> VortexResult> { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + Ok(Some(crate::aggregate_fn::fns::is_constant::is_constant( + array, &mut ctx, + )?)) } /// When calling `is_constant` the children are all checked for constantness. @@ -269,11 +39,8 @@ pub enum Cost { /// Only apply constant time computation to estimate constantness. Negligible, /// Allow the encoding to do a linear amount of work to determine is constant. - /// Each encoding should implement short-circuiting make the common case runtime well below - /// a linear scan. Specialized, /// Same as linear, but when necessary canonicalize the array and check is constant. - /// This *must* always return a known answer. Canonicalize, } @@ -292,7 +59,7 @@ impl Default for IsConstantOpts { } } -impl Options for IsConstantOpts { +impl super::Options for IsConstantOpts { fn as_any(&self) -> &dyn Any { self } @@ -303,48 +70,3 @@ impl IsConstantOpts { self.cost == Cost::Negligible } } - -#[cfg(test)] -mod tests { - use vortex_buffer::buffer; - - use crate::IntoArray as _; - use crate::arrays::PrimitiveArray; - use crate::compute::is_constant; - use crate::expr::stats::Stat; - - #[test] - fn is_constant_min_max_no_nan() { - let arr = buffer![0, 1].into_array(); - arr.statistics() - .compute_all(&[Stat::Min, Stat::Max]) - .unwrap(); - assert!(!is_constant(&arr).unwrap().unwrap_or_default()); - - let arr = buffer![0, 0].into_array(); - arr.statistics() - .compute_all(&[Stat::Min, Stat::Max]) - .unwrap(); - assert!(is_constant(&arr).unwrap().unwrap_or_default()); - - let arr = PrimitiveArray::from_option_iter([Some(0), Some(0)]).into_array(); - assert!(is_constant(&arr).unwrap().unwrap_or_default()); - } - - #[test] - fn is_constant_min_max_with_nan() { - let arr = PrimitiveArray::from_iter([0.0, 0.0, f32::NAN]).into_array(); - arr.statistics() - .compute_all(&[Stat::Min, Stat::Max]) - .unwrap(); - assert!(!is_constant(&arr).unwrap().unwrap_or_default()); - - let arr = - PrimitiveArray::from_option_iter([Some(f32::NEG_INFINITY), Some(f32::NEG_INFINITY)]) - .into_array(); - arr.statistics() - .compute_all(&[Stat::Min, Stat::Max]) - .unwrap(); - assert!(is_constant(&arr).unwrap().unwrap_or_default()); - } -} diff --git a/vortex-array/src/compute/mod.rs b/vortex-array/src/compute/mod.rs index 7bbc257085f..7c1fedfca0d 100644 --- a/vortex-array/src/compute/mod.rs +++ b/vortex-array/src/compute/mod.rs @@ -56,10 +56,8 @@ pub struct ComputeFn { /// /// Mostly useful for small benchmarks where the overhead might cause noise depending on the order of benchmarks. pub fn warm_up_vtables() { - #[allow(unused_qualifications)] - is_constant::warm_up_vtable(); - is_sorted::warm_up_vtable(); - // min_max and nan_count have been migrated to aggregate_fn + warm_up_vtable(); + // min_max, nan_count, and is_constant have been migrated to aggregate_fn } impl ComputeFn { diff --git a/vortex-array/src/stats/array.rs b/vortex-array/src/stats/array.rs index be1ad766088..3f018f64b35 100644 --- a/vortex-array/src/stats/array.rs +++ b/vortex-array/src/stats/array.rs @@ -17,12 +17,12 @@ use super::TypedStatsSetRef; use crate::DynArray; use crate::LEGACY_SESSION; use crate::VortexSessionExecute; +use crate::aggregate_fn::fns::is_constant::is_constant; use crate::aggregate_fn::fns::min_max::MinMaxResult; use crate::aggregate_fn::fns::min_max::min_max; use crate::aggregate_fn::fns::nan_count::nan_count; use crate::aggregate_fn::fns::sum::sum; use crate::builders::builder_with_capacity; -use crate::compute::is_constant; use crate::compute::is_sorted; use crate::compute::is_strict_sorted; use crate::expr::stats::Precision; @@ -170,7 +170,7 @@ impl StatsSetRef<'_> { if self.dyn_array_ref.is_empty() { None } else { - is_constant(&array_ref)?.map(|v| v.into()) + Some(is_constant(&array_ref, &mut ctx)?.into()) } } Stat::IsSorted => is_sorted(&array_ref)?.map(|v| v.into()), diff --git a/vortex-btrblocks/src/canonical_compressor.rs b/vortex-btrblocks/src/canonical_compressor.rs index ca3dc2a05c9..410dda0b599 100644 --- a/vortex-btrblocks/src/canonical_compressor.rs +++ b/vortex-btrblocks/src/canonical_compressor.rs @@ -11,6 +11,7 @@ use vortex_array::IntoArray; use vortex_array::LEGACY_SESSION; use vortex_array::ToCanonical; use vortex_array::VortexSessionExecute; +use vortex_array::aggregate_fn::fns::is_constant::is_constant; use vortex_array::arrays::ConstantArray; use vortex_array::arrays::ExtensionArray; use vortex_array::arrays::FixedSizeListArray; @@ -19,9 +20,6 @@ use vortex_array::arrays::ListViewArray; use vortex_array::arrays::StructArray; use vortex_array::arrays::TemporalArray; use vortex_array::arrays::listview::list_from_list_view; -use vortex_array::compute::Cost; -use vortex_array::compute::IsConstantOpts; -use vortex_array::compute::is_constant_opts; use vortex_array::dtype::DType; use vortex_array::dtype::Nullability; use vortex_array::extension::datetime::TemporalMetadata; @@ -281,14 +279,8 @@ impl CanonicalCompressor for BtrBlocksCompressor { if let Ok(temporal_array) = TemporalArray::try_from(ext_array.clone().into_array()) && let TemporalMetadata::Timestamp(..) = temporal_array.temporal_metadata() { - if is_constant_opts( - &ext_array.clone().into_array(), - &IsConstantOpts { - cost: Cost::Canonicalize, - }, - )? - .unwrap_or_default() - { + let mut ctx = LEGACY_SESSION.create_execution_ctx(); + if is_constant(&ext_array.clone().into_array(), &mut ctx)? { return Ok(ConstantArray::new( temporal_array.as_ref().scalar_at(0)?, ext_array.len(), diff --git a/vortex-btrblocks/src/compressor/string.rs b/vortex-btrblocks/src/compressor/string.rs index 66b703103ee..cac9cf969cd 100644 --- a/vortex-btrblocks/src/compressor/string.rs +++ b/vortex-btrblocks/src/compressor/string.rs @@ -8,7 +8,10 @@ use enum_iterator::Sequence; use vortex_array::ArrayRef; use vortex_array::Canonical; use vortex_array::IntoArray; +use vortex_array::LEGACY_SESSION; use vortex_array::ToCanonical; +use vortex_array::VortexSessionExecute; +use vortex_array::aggregate_fn::fns::is_constant::is_constant; use vortex_array::arrays::ConstantArray; use vortex_array::arrays::DictArray; use vortex_array::arrays::MaskedArray; @@ -16,7 +19,6 @@ use vortex_array::arrays::VarBinArray; use vortex_array::arrays::VarBinView; use vortex_array::arrays::VarBinViewArray; use vortex_array::builders::dict::dict_encode; -use vortex_array::compute::is_constant; use vortex_array::scalar::Scalar; use vortex_array::vtable::VTable; use vortex_array::vtable::ValidityHelper; @@ -413,8 +415,9 @@ impl Scheme for ConstantScheme { return Ok(0.0); } + let mut ctx = LEGACY_SESSION.create_execution_ctx(); if stats.estimated_distinct_count > 1 - || !is_constant(&stats.src.clone().into_array())?.unwrap_or(false) + || !is_constant(&stats.src.clone().into_array(), &mut ctx)? { return Ok(0.0); } diff --git a/vortex-file/src/lib.rs b/vortex-file/src/lib.rs index 8de68eca76f..bf6114cb20e 100644 --- a/vortex-file/src/lib.rs +++ b/vortex-file/src/lib.rs @@ -112,12 +112,6 @@ pub use strategy::*; use vortex_array::arrays::Dict; use vortex_array::session::ArraySessionExt; use vortex_bytebool::ByteBool; -use vortex_datetime_parts::DateTimeParts; -use vortex_decimal_byte_parts::DecimalByteParts; -use vortex_fastlanes::BitPacked; -use vortex_fastlanes::Delta; -use vortex_fastlanes::FoR; -use vortex_fastlanes::RLE; use vortex_fsst::FSST; use vortex_pco::Pco; use vortex_session::VortexSession; @@ -164,16 +158,10 @@ mod forever_constant { pub fn register_default_encodings(session: &mut VortexSession) { { let arrays = session.arrays(); - arrays.register(BitPacked::ID, BitPacked); arrays.register(ByteBool::ID, ByteBool); - arrays.register(DateTimeParts::ID, DateTimeParts); - arrays.register(DecimalByteParts::ID, DecimalByteParts); - arrays.register(Delta::ID, Delta); arrays.register(Dict::ID, Dict); arrays.register(FSST::ID, FSST); - arrays.register(FoR::ID, FoR); arrays.register(Pco::ID, Pco); - arrays.register(RLE::ID, RLE); arrays.register(Sparse::ID, Sparse); arrays.register(ZigZag::ID, ZigZag); #[cfg(feature = "zstd")] @@ -185,6 +173,9 @@ pub fn register_default_encodings(session: &mut VortexSession) { // Eventually all encodings crates should expose an initialize function. For now it's only // a few of them. vortex_alp::initialize(session); + vortex_datetime_parts::initialize(session); + vortex_decimal_byte_parts::initialize(session); + vortex_fastlanes::initialize(session); vortex_runend::initialize(session); vortex_sequence::initialize(session); } From 638031b94f8a4302cffc029b68135c3d89e652f0 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 12:38:06 -0700 Subject: [PATCH 2/9] is-constant Signed-off-by: Nicholas Gates --- encodings/datetime-parts/public-api.lock | 6 +- encodings/decimal-byte-parts/public-api.lock | 6 +- encodings/fastlanes/public-api.lock | 10 +- encodings/runend/public-api.lock | 4 - vortex-array/public-api.lock | 268 ++++++------------- 5 files changed, 95 insertions(+), 199 deletions(-) diff --git a/encodings/datetime-parts/public-api.lock b/encodings/datetime-parts/public-api.lock index f8c1c15d076..475c7da5168 100644 --- a/encodings/datetime-parts/public-api.lock +++ b/encodings/datetime-parts/public-api.lock @@ -22,10 +22,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_datetime_parts::DateTim pub fn vortex_datetime_parts::DateTimeParts::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::is_constant::IsConstantKernel for vortex_datetime_parts::DateTimeParts - -pub fn vortex_datetime_parts::DateTimeParts::is_constant(&self, array: &vortex_datetime_parts::DateTimePartsArray, opts: &vortex_array::compute::is_constant::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::scalar_fn::fns::binary::compare::CompareKernel for vortex_datetime_parts::DateTimeParts pub fn vortex_datetime_parts::DateTimeParts::compare(lhs: &vortex_datetime_parts::DateTimePartsArray, rhs: &vortex_array::array::ArrayRef, operator: vortex_array::scalar_fn::fns::operators::CompareOperator, _ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult> @@ -220,4 +216,6 @@ pub vortex_datetime_parts::TemporalParts::seconds: vortex_array::array::ArrayRef pub vortex_datetime_parts::TemporalParts::subseconds: vortex_array::array::ArrayRef +pub fn vortex_datetime_parts::initialize(session: &mut vortex_session::VortexSession) + pub fn vortex_datetime_parts::split_temporal(array: vortex_array::arrays::datetime::TemporalArray) -> vortex_error::VortexResult diff --git a/encodings/decimal-byte-parts/public-api.lock b/encodings/decimal-byte-parts/public-api.lock index e805c8a1b07..cf9c86b6d1a 100644 --- a/encodings/decimal-byte-parts/public-api.lock +++ b/encodings/decimal-byte-parts/public-api.lock @@ -22,10 +22,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_decimal_byte_parts::Dec pub fn vortex_decimal_byte_parts::DecimalByteParts::slice(array: &vortex_decimal_byte_parts::DecimalBytePartsArray, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::is_constant::IsConstantKernel for vortex_decimal_byte_parts::DecimalByteParts - -pub fn vortex_decimal_byte_parts::DecimalByteParts::is_constant(&self, array: &vortex_decimal_byte_parts::DecimalBytePartsArray, opts: &vortex_array::compute::is_constant::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::scalar_fn::fns::binary::compare::CompareKernel for vortex_decimal_byte_parts::DecimalByteParts pub fn vortex_decimal_byte_parts::DecimalByteParts::compare(lhs: &Self::Array, rhs: &vortex_array::array::ArrayRef, operator: vortex_array::scalar_fn::fns::operators::CompareOperator, _ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult> @@ -167,3 +163,5 @@ impl prost::message::Message for vortex_decimal_byte_parts::DecimalBytesPartsMet pub fn vortex_decimal_byte_parts::DecimalBytesPartsMetadata::clear(&mut self) pub fn vortex_decimal_byte_parts::DecimalBytesPartsMetadata::encoded_len(&self) -> usize + +pub fn vortex_decimal_byte_parts::initialize(session: &mut vortex_session::VortexSession) diff --git a/encodings/fastlanes/public-api.lock b/encodings/fastlanes/public-api.lock index 8b6cd3acb17..31ed7a06c02 100644 --- a/encodings/fastlanes/public-api.lock +++ b/encodings/fastlanes/public-api.lock @@ -134,10 +134,6 @@ impl vortex_array::arrays::slice::SliceKernel for vortex_fastlanes::BitPacked pub fn vortex_fastlanes::BitPacked::slice(array: &vortex_fastlanes::BitPackedArray, range: core::ops::range::Range, _ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult> -impl vortex_array::compute::is_constant::IsConstantKernel for vortex_fastlanes::BitPacked - -pub fn vortex_fastlanes::BitPacked::is_constant(&self, array: &vortex_fastlanes::BitPackedArray, opts: &vortex_array::compute::is_constant::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::scalar_fn::fns::cast::kernel::CastReduce for vortex_fastlanes::BitPacked pub fn vortex_fastlanes::BitPacked::cast(array: &vortex_fastlanes::BitPackedArray, dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult> @@ -424,10 +420,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_fastlanes::FoR pub fn vortex_fastlanes::FoR::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::is_constant::IsConstantKernel for vortex_fastlanes::FoR - -pub fn vortex_fastlanes::FoR::is_constant(&self, array: &vortex_fastlanes::FoRArray, opts: &vortex_array::compute::is_constant::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::is_sorted::IsSortedKernel for vortex_fastlanes::FoR pub fn vortex_fastlanes::FoR::is_sorted(&self, array: &vortex_fastlanes::FoRArray) -> vortex_error::VortexResult> @@ -683,3 +675,5 @@ impl vortex_array::vtable::validity::ValidityChildSliceHelper for vortex_fastlan pub fn vortex_fastlanes::RLEArray::unsliced_child_and_slice(&self) -> (&vortex_array::array::ArrayRef, usize, usize) pub fn vortex_fastlanes::delta_compress(array: &vortex_array::arrays::primitive::array::PrimitiveArray) -> vortex_error::VortexResult<(vortex_array::arrays::primitive::array::PrimitiveArray, vortex_array::arrays::primitive::array::PrimitiveArray)> + +pub fn vortex_fastlanes::initialize(session: &mut vortex_session::VortexSession) diff --git a/encodings/runend/public-api.lock b/encodings/runend/public-api.lock index a5e8fa3fdcf..c4c4bb326b4 100644 --- a/encodings/runend/public-api.lock +++ b/encodings/runend/public-api.lock @@ -34,10 +34,6 @@ impl vortex_array::arrays::filter::kernel::FilterKernel for vortex_runend::RunEn pub fn vortex_runend::RunEnd::filter(array: &vortex_runend::RunEndArray, mask: &vortex_mask::Mask, ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult> -impl vortex_array::compute::is_constant::IsConstantKernel for vortex_runend::RunEnd - -pub fn vortex_runend::RunEnd::is_constant(&self, array: &Self::Array, opts: &vortex_array::compute::is_constant::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::is_sorted::IsSortedKernel for vortex_runend::RunEnd pub fn vortex_runend::RunEnd::is_sorted(&self, array: &vortex_runend::RunEndArray) -> vortex_error::VortexResult> diff --git a/vortex-array/public-api.lock b/vortex-array/public-api.lock index ea2e3619b1e..e9911df785d 100644 --- a/vortex-array/public-api.lock +++ b/vortex-array/public-api.lock @@ -30,6 +30,62 @@ pub mod vortex_array::aggregate_fn pub mod vortex_array::aggregate_fn::fns +pub mod vortex_array::aggregate_fn::fns::is_constant + +pub mod vortex_array::aggregate_fn::fns::is_constant::primitive + +pub const vortex_array::aggregate_fn::fns::is_constant::primitive::IS_CONST_LANE_WIDTH: usize + +pub fn vortex_array::aggregate_fn::fns::is_constant::primitive::compute_is_constant(values: &[T]) -> bool + +pub struct vortex_array::aggregate_fn::fns::is_constant::IsConstant + +impl core::clone::Clone for vortex_array::aggregate_fn::fns::is_constant::IsConstant + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::clone(&self) -> vortex_array::aggregate_fn::fns::is_constant::IsConstant + +impl core::fmt::Debug for vortex_array::aggregate_fn::fns::is_constant::IsConstant + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result + +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::is_constant::IsConstant + +pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Partial = vortex_array::aggregate_fn::fns::is_constant::IsConstantPartial + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::accumulate(&self, partial: &mut Self::Partial, batch: &vortex_array::Columnar, _ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::coerce_args(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::combine_partials(&self, partial: &mut Self::Partial, other: vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::deserialize(&self, _metadata: &[u8], _session: &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::empty_partial(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::finalize(&self, partials: vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::finalize_scalar(&self, partial: vortex_array::scalar::Scalar) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::flush(&self, partial: &mut Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::is_saturated(&self, partial: &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::partial_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::return_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> + +pub struct vortex_array::aggregate_fn::fns::is_constant::IsConstantPartial + +pub fn vortex_array::aggregate_fn::fns::is_constant::is_constant(array: &vortex_array::ArrayRef, ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype(element_dtype: &vortex_array::dtype::DType) -> vortex_array::dtype::DType + pub mod vortex_array::aggregate_fn::fns::min_max pub struct vortex_array::aggregate_fn::fns::min_max::MinMax @@ -444,6 +500,38 @@ pub fn vortex_array::aggregate_fn::AggregateFnVTable::return_dtype(&self, option pub fn vortex_array::aggregate_fn::AggregateFnVTable::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> +impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::is_constant::IsConstant + +pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Options = vortex_array::aggregate_fn::EmptyOptions + +pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Partial = vortex_array::aggregate_fn::fns::is_constant::IsConstantPartial + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::accumulate(&self, partial: &mut Self::Partial, batch: &vortex_array::Columnar, _ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::coerce_args(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::combine_partials(&self, partial: &mut Self::Partial, other: vortex_array::scalar::Scalar) -> vortex_error::VortexResult<()> + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::deserialize(&self, _metadata: &[u8], _session: &vortex_session::VortexSession) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::empty_partial(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::finalize(&self, partials: vortex_array::ArrayRef) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::finalize_scalar(&self, partial: vortex_array::scalar::Scalar) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::flush(&self, partial: &mut Self::Partial) -> vortex_error::VortexResult + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::id(&self) -> vortex_array::aggregate_fn::AggregateFnId + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::is_saturated(&self, partial: &Self::Partial) -> bool + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::partial_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::return_dtype(&self, _options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> core::option::Option + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::serialize(&self, options: &Self::Options) -> vortex_error::VortexResult>> + impl vortex_array::aggregate_fn::AggregateFnVTable for vortex_array::aggregate_fn::fns::min_max::MinMax pub type vortex_array::aggregate_fn::fns::min_max::MinMax::Options = vortex_array::aggregate_fn::EmptyOptions @@ -618,10 +706,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Bool pub fn vortex_array::arrays::Bool::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Bool - -pub fn vortex_array::arrays::Bool::is_constant(&self, array: &vortex_array::arrays::BoolArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Bool pub fn vortex_array::arrays::Bool::is_sorted(&self, array: &vortex_array::arrays::BoolArray) -> vortex_error::VortexResult> @@ -838,10 +922,6 @@ impl vortex_array::arrays::slice::SliceKernel for vortex_array::arrays::Chunked pub fn vortex_array::arrays::Chunked::slice(array: &Self::Array, range: core::ops::range::Range, _ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Chunked - -pub fn vortex_array::arrays::Chunked::is_constant(&self, array: &vortex_array::arrays::ChunkedArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Chunked pub fn vortex_array::arrays::Chunked::is_sorted(&self, array: &vortex_array::arrays::ChunkedArray) -> vortex_error::VortexResult> @@ -1214,10 +1294,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Decimal pub fn vortex_array::arrays::Decimal::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Decimal - -pub fn vortex_array::arrays::Decimal::is_constant(&self, array: &vortex_array::arrays::DecimalArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Decimal pub fn vortex_array::arrays::Decimal::is_sorted(&self, array: &vortex_array::arrays::DecimalArray) -> vortex_error::VortexResult> @@ -1434,10 +1510,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::dict::Di pub fn vortex_array::arrays::dict::Dict::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::dict::Dict - -pub fn vortex_array::arrays::dict::Dict::is_constant(&self, array: &vortex_array::arrays::dict::DictArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::dict::Dict pub fn vortex_array::arrays::dict::Dict::is_sorted(&self, array: &vortex_array::arrays::dict::DictArray) -> vortex_error::VortexResult> @@ -1548,10 +1620,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::dict::Di pub fn vortex_array::arrays::dict::Dict::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::dict::Dict - -pub fn vortex_array::arrays::dict::Dict::is_constant(&self, array: &vortex_array::arrays::dict::DictArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::dict::Dict pub fn vortex_array::arrays::dict::Dict::is_sorted(&self, array: &vortex_array::arrays::dict::DictArray) -> vortex_error::VortexResult> @@ -1862,10 +1930,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Extensio pub fn vortex_array::arrays::Extension::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Extension - -pub fn vortex_array::arrays::Extension::is_constant(&self, array: &vortex_array::arrays::ExtensionArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Extension pub fn vortex_array::arrays::Extension::is_sorted(&self, array: &vortex_array::arrays::ExtensionArray) -> vortex_error::VortexResult> @@ -2228,10 +2292,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::FixedSiz pub fn vortex_array::arrays::FixedSizeList::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::FixedSizeList - -pub fn vortex_array::arrays::FixedSizeList::is_constant(&self, array: &vortex_array::arrays::FixedSizeListArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::FixedSizeList pub fn vortex_array::arrays::FixedSizeList::is_sorted(&self, _array: &vortex_array::arrays::FixedSizeListArray) -> vortex_error::VortexResult> @@ -2386,10 +2446,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::List pub fn vortex_array::arrays::List::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::List - -pub fn vortex_array::arrays::List::is_constant(&self, array: &vortex_array::arrays::ListArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::List pub fn vortex_array::arrays::List::is_sorted(&self, _array: &vortex_array::arrays::ListArray) -> vortex_error::VortexResult> @@ -2564,10 +2620,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::ListView pub fn vortex_array::arrays::ListView::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::ListView - -pub fn vortex_array::arrays::ListView::is_constant(&self, array: &vortex_array::arrays::ListViewArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::ListView pub fn vortex_array::arrays::ListView::is_sorted(&self, _array: &vortex_array::arrays::ListViewArray) -> vortex_error::VortexResult> @@ -3086,10 +3138,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Primitiv pub fn vortex_array::arrays::Primitive::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Primitive - -pub fn vortex_array::arrays::Primitive::is_constant(&self, array: &vortex_array::arrays::PrimitiveArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Primitive pub fn vortex_array::arrays::Primitive::is_sorted(&self, array: &vortex_array::arrays::PrimitiveArray) -> vortex_error::VortexResult> @@ -3310,12 +3358,8 @@ pub type vortex_array::arrays::primitive::PrimitiveMaskedValidityRule::Parent = pub fn vortex_array::arrays::primitive::PrimitiveMaskedValidityRule::reduce_parent(&self, array: &vortex_array::arrays::PrimitiveArray, parent: &vortex_array::arrays::MaskedArray, _child_idx: usize) -> vortex_error::VortexResult> -pub const vortex_array::arrays::primitive::IS_CONST_LANE_WIDTH: usize - pub fn vortex_array::arrays::primitive::chunk_range(chunk_idx: usize, offset: usize, array_len: usize) -> core::ops::range::Range -pub fn vortex_array::arrays::primitive::compute_is_constant(values: &[T]) -> bool - pub fn vortex_array::arrays::primitive::patch_chunk(decoded_values: &mut [T], patches_indices: &[I], patches_values: &[T], patches_offset: usize, chunk_offsets_slice: &[C], chunk_idx: usize, offset_within_chunk: usize) where T: vortex_array::dtype::NativePType, I: vortex_array::dtype::UnsignedPType, C: vortex_array::dtype::UnsignedPType pub mod vortex_array::arrays::scalar_fn @@ -3876,10 +3920,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Struct pub fn vortex_array::arrays::Struct::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Struct - -pub fn vortex_array::arrays::Struct::is_constant(&self, array: &vortex_array::arrays::StructArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::scalar_fn::fns::cast::CastKernel for vortex_array::arrays::Struct pub fn vortex_array::arrays::Struct::cast(array: &vortex_array::arrays::StructArray, dtype: &vortex_array::dtype::DType, _ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult> @@ -4096,10 +4136,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::VarBin pub fn vortex_array::arrays::VarBin::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::VarBin - -pub fn vortex_array::arrays::VarBin::is_constant(&self, array: &vortex_array::arrays::VarBinArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::VarBin pub fn vortex_array::arrays::VarBin::is_sorted(&self, array: &vortex_array::arrays::VarBinArray) -> vortex_error::VortexResult> @@ -4508,10 +4544,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::VarBinVi pub fn vortex_array::arrays::VarBinView::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::VarBinView - -pub fn vortex_array::arrays::VarBinView::is_constant(&self, array: &vortex_array::arrays::VarBinViewArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::VarBinView pub fn vortex_array::arrays::VarBinView::is_sorted(&self, array: &vortex_array::arrays::VarBinViewArray) -> vortex_error::VortexResult> @@ -4730,10 +4762,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Bool pub fn vortex_array::arrays::Bool::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Bool - -pub fn vortex_array::arrays::Bool::is_constant(&self, array: &vortex_array::arrays::BoolArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Bool pub fn vortex_array::arrays::Bool::is_sorted(&self, array: &vortex_array::arrays::BoolArray) -> vortex_error::VortexResult> @@ -4922,10 +4950,6 @@ impl vortex_array::arrays::slice::SliceKernel for vortex_array::arrays::Chunked pub fn vortex_array::arrays::Chunked::slice(array: &Self::Array, range: core::ops::range::Range, _ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Chunked - -pub fn vortex_array::arrays::Chunked::is_constant(&self, array: &vortex_array::arrays::ChunkedArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Chunked pub fn vortex_array::arrays::Chunked::is_sorted(&self, array: &vortex_array::arrays::ChunkedArray) -> vortex_error::VortexResult> @@ -5232,10 +5256,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Decimal pub fn vortex_array::arrays::Decimal::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Decimal - -pub fn vortex_array::arrays::Decimal::is_constant(&self, array: &vortex_array::arrays::DecimalArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Decimal pub fn vortex_array::arrays::Decimal::is_sorted(&self, array: &vortex_array::arrays::DecimalArray) -> vortex_error::VortexResult> @@ -5420,10 +5440,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::dict::Di pub fn vortex_array::arrays::dict::Dict::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::dict::Dict - -pub fn vortex_array::arrays::dict::Dict::is_constant(&self, array: &vortex_array::arrays::dict::DictArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::dict::Dict pub fn vortex_array::arrays::dict::Dict::is_sorted(&self, array: &vortex_array::arrays::dict::DictArray) -> vortex_error::VortexResult> @@ -5592,10 +5608,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Extensio pub fn vortex_array::arrays::Extension::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Extension - -pub fn vortex_array::arrays::Extension::is_constant(&self, array: &vortex_array::arrays::ExtensionArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Extension pub fn vortex_array::arrays::Extension::is_sorted(&self, array: &vortex_array::arrays::ExtensionArray) -> vortex_error::VortexResult> @@ -5872,10 +5884,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::FixedSiz pub fn vortex_array::arrays::FixedSizeList::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::FixedSizeList - -pub fn vortex_array::arrays::FixedSizeList::is_constant(&self, array: &vortex_array::arrays::FixedSizeListArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::FixedSizeList pub fn vortex_array::arrays::FixedSizeList::is_sorted(&self, _array: &vortex_array::arrays::FixedSizeListArray) -> vortex_error::VortexResult> @@ -6028,10 +6036,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::List pub fn vortex_array::arrays::List::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::List - -pub fn vortex_array::arrays::List::is_constant(&self, array: &vortex_array::arrays::ListArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::List pub fn vortex_array::arrays::List::is_sorted(&self, _array: &vortex_array::arrays::ListArray) -> vortex_error::VortexResult> @@ -6184,10 +6188,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::ListView pub fn vortex_array::arrays::ListView::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::ListView - -pub fn vortex_array::arrays::ListView::is_constant(&self, array: &vortex_array::arrays::ListViewArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::ListView pub fn vortex_array::arrays::ListView::is_sorted(&self, _array: &vortex_array::arrays::ListViewArray) -> vortex_error::VortexResult> @@ -6614,10 +6614,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Primitiv pub fn vortex_array::arrays::Primitive::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Primitive - -pub fn vortex_array::arrays::Primitive::is_constant(&self, array: &vortex_array::arrays::PrimitiveArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::Primitive pub fn vortex_array::arrays::Primitive::is_sorted(&self, array: &vortex_array::arrays::PrimitiveArray) -> vortex_error::VortexResult> @@ -7192,10 +7188,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::Struct pub fn vortex_array::arrays::Struct::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Struct - -pub fn vortex_array::arrays::Struct::is_constant(&self, array: &vortex_array::arrays::StructArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::scalar_fn::fns::cast::CastKernel for vortex_array::arrays::Struct pub fn vortex_array::arrays::Struct::cast(array: &vortex_array::arrays::StructArray, dtype: &vortex_array::dtype::DType, _ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult> @@ -7436,10 +7428,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::VarBin pub fn vortex_array::arrays::VarBin::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::VarBin - -pub fn vortex_array::arrays::VarBin::is_constant(&self, array: &vortex_array::arrays::VarBinArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::VarBin pub fn vortex_array::arrays::VarBin::is_sorted(&self, array: &vortex_array::arrays::VarBinArray) -> vortex_error::VortexResult> @@ -7662,10 +7650,6 @@ impl vortex_array::arrays::slice::SliceReduce for vortex_array::arrays::VarBinVi pub fn vortex_array::arrays::VarBinView::slice(array: &Self::Array, range: core::ops::range::Range) -> vortex_error::VortexResult> -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::VarBinView - -pub fn vortex_array::arrays::VarBinView::is_constant(&self, array: &vortex_array::arrays::VarBinViewArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - impl vortex_array::compute::IsSortedKernel for vortex_array::arrays::VarBinView pub fn vortex_array::arrays::VarBinView::is_sorted(&self, array: &vortex_array::arrays::VarBinViewArray) -> vortex_error::VortexResult> @@ -9584,24 +9568,6 @@ impl<'a> core::clone::Clone for vortex_array::compute::InvocationArgs<'a> pub fn vortex_array::compute::InvocationArgs<'a>::clone(&self) -> vortex_array::compute::InvocationArgs<'a> -pub struct vortex_array::compute::IsConstantKernelAdapter(pub V) - -impl vortex_array::compute::IsConstantKernelAdapter - -pub const fn vortex_array::compute::IsConstantKernelAdapter::lift(&'static self) -> vortex_array::compute::IsConstantKernelRef - -impl core::fmt::Debug for vortex_array::compute::IsConstantKernelAdapter - -pub fn vortex_array::compute::IsConstantKernelAdapter::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result - -impl vortex_array::compute::Kernel for vortex_array::compute::IsConstantKernelAdapter - -pub fn vortex_array::compute::IsConstantKernelAdapter::invoke(&self, args: &vortex_array::compute::InvocationArgs<'_>) -> vortex_error::VortexResult> - -pub struct vortex_array::compute::IsConstantKernelRef(_) - -impl inventory::Collect for vortex_array::compute::IsConstantKernelRef - pub struct vortex_array::compute::IsConstantOpts pub vortex_array::compute::IsConstantOpts::cost: vortex_array::compute::Cost @@ -9692,58 +9658,6 @@ pub fn vortex_array::compute::ComputeFnVTable::return_dtype(&self, args: &vortex pub fn vortex_array::compute::ComputeFnVTable::return_len(&self, args: &vortex_array::compute::InvocationArgs<'_>) -> vortex_error::VortexResult -pub trait vortex_array::compute::IsConstantKernel: vortex_array::vtable::VTable - -pub fn vortex_array::compute::IsConstantKernel::is_constant(&self, array: &Self::Array, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Bool - -pub fn vortex_array::arrays::Bool::is_constant(&self, array: &vortex_array::arrays::BoolArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Chunked - -pub fn vortex_array::arrays::Chunked::is_constant(&self, array: &vortex_array::arrays::ChunkedArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Decimal - -pub fn vortex_array::arrays::Decimal::is_constant(&self, array: &vortex_array::arrays::DecimalArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Extension - -pub fn vortex_array::arrays::Extension::is_constant(&self, array: &vortex_array::arrays::ExtensionArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::FixedSizeList - -pub fn vortex_array::arrays::FixedSizeList::is_constant(&self, array: &vortex_array::arrays::FixedSizeListArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::List - -pub fn vortex_array::arrays::List::is_constant(&self, array: &vortex_array::arrays::ListArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::ListView - -pub fn vortex_array::arrays::ListView::is_constant(&self, array: &vortex_array::arrays::ListViewArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Primitive - -pub fn vortex_array::arrays::Primitive::is_constant(&self, array: &vortex_array::arrays::PrimitiveArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::Struct - -pub fn vortex_array::arrays::Struct::is_constant(&self, array: &vortex_array::arrays::StructArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::VarBin - -pub fn vortex_array::arrays::VarBin::is_constant(&self, array: &vortex_array::arrays::VarBinArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::VarBinView - -pub fn vortex_array::arrays::VarBinView::is_constant(&self, array: &vortex_array::arrays::VarBinViewArray, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - -impl vortex_array::compute::IsConstantKernel for vortex_array::arrays::dict::Dict - -pub fn vortex_array::arrays::dict::Dict::is_constant(&self, array: &vortex_array::arrays::dict::DictArray, opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> - pub trait vortex_array::compute::IsSortedIteratorExt where ::Item: core::cmp::PartialOrd: core::iter::traits::iterator::Iterator pub fn vortex_array::compute::IsSortedIteratorExt::is_strict_sorted(self) -> bool where Self: core::marker::Sized, Self::Item: core::cmp::PartialOrd @@ -9828,10 +9742,6 @@ pub trait vortex_array::compute::Kernel: 'static + core::marker::Send + core::ma pub fn vortex_array::compute::Kernel::invoke(&self, args: &vortex_array::compute::InvocationArgs<'_>) -> vortex_error::VortexResult> -impl vortex_array::compute::Kernel for vortex_array::compute::IsConstantKernelAdapter - -pub fn vortex_array::compute::IsConstantKernelAdapter::invoke(&self, args: &vortex_array::compute::InvocationArgs<'_>) -> vortex_error::VortexResult> - impl vortex_array::compute::Kernel for vortex_array::compute::IsSortedKernelAdapter pub fn vortex_array::compute::IsSortedKernelAdapter::invoke(&self, args: &vortex_array::compute::InvocationArgs<'_>) -> vortex_error::VortexResult> @@ -9854,7 +9764,7 @@ pub fn vortex_array::scalar_fn::fns::between::BetweenOptions::as_any(&self) -> & pub fn vortex_array::compute::is_constant(array: &vortex_array::ArrayRef) -> vortex_error::VortexResult> -pub fn vortex_array::compute::is_constant_opts(array: &vortex_array::ArrayRef, options: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> +pub fn vortex_array::compute::is_constant_opts(array: &vortex_array::ArrayRef, _opts: &vortex_array::compute::IsConstantOpts) -> vortex_error::VortexResult> pub fn vortex_array::compute::is_sorted(array: &vortex_array::ArrayRef) -> vortex_error::VortexResult> From 47bd65039884b7978ab3c237a2e469681533bc30 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 13:04:34 -0700 Subject: [PATCH 3/9] is-constant Signed-off-by: Nicholas Gates --- .../src/aggregate_fn/fns/is_constant/mod.rs | 100 +++++++++--------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs index d2b4156e3dd..72aadbdba5c 100644 --- a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs @@ -3,14 +3,23 @@ mod bool; mod decimal; +mod extension; +mod fixed_size_list; +mod list; pub mod primitive; +mod struct_; mod varbin; use vortex_error::VortexResult; +use vortex_mask::Mask; use self::bool::check_bool_constant; use self::decimal::check_decimal_constant; +use self::extension::check_extension_constant; +use self::fixed_size_list::check_fixed_size_list_constant; +use self::list::check_listview_constant; use self::primitive::check_primitive_constant; +use self::struct_::check_struct_constant; use self::varbin::check_varbinview_constant; use crate::ArrayRef; use crate::Canonical; @@ -18,13 +27,16 @@ use crate::Columnar; use crate::DynArray; use crate::ExecutionCtx; use crate::IntoArray; +use crate::ToCanonical; use crate::aggregate_fn::Accumulator; use crate::aggregate_fn::AggregateFnId; use crate::aggregate_fn::AggregateFnVTable; use crate::aggregate_fn::DynAccumulator; use crate::aggregate_fn::EmptyOptions; +use crate::arrays::BoolArray; use crate::arrays::Constant; use crate::arrays::Null; +use crate::builtins::ArrayBuiltins; use crate::dtype::DType; use crate::dtype::FieldNames; use crate::dtype::Nullability; @@ -34,6 +46,37 @@ use crate::expr::stats::Stat; use crate::expr::stats::StatsProvider; use crate::expr::stats::StatsProviderExt; use crate::scalar::Scalar; +use crate::scalar_fn::fns::operators::Operator; + +/// Check if two arrays of the same length have equal values at every position (null-safe). +/// +/// Two positions are considered equal if they are both null, or both non-null with the same value. +fn arrays_value_equal(a: &ArrayRef, b: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { + debug_assert_eq!(a.len(), b.len()); + if a.is_empty() { + return Ok(true); + } + + // Check validity masks match (null positions must be identical). + let a_mask = a.validity_mask()?; + let b_mask = b.validity_mask()?; + if a_mask != b_mask { + return Ok(false); + } + + let valid_count = a_mask.true_count(); + if valid_count == 0 { + // Both all-null → equal. + return Ok(true); + } + + // Compare values element-wise. Result is null where both inputs are null, + // true/false where both are valid. + let eq_result = a.binary(b.clone(), Operator::Eq)?; + let eq_result = eq_result.execute::(ctx)?; + + Ok(eq_result.true_count() == valid_count) +} /// Compute whether an array has constant values. /// @@ -275,7 +318,7 @@ impl AggregateFnVTable for IsConstant { &self, partial: &mut Self::Partial, batch: &Columnar, - _ctx: &mut ExecutionCtx, + ctx: &mut ExecutionCtx, ) -> VortexResult<()> { if !partial.is_constant { return Ok(()); @@ -318,57 +361,10 @@ impl AggregateFnVTable for IsConstant { Canonical::Bool(b) => check_bool_constant(b), Canonical::VarBinView(v) => check_varbinview_constant(v), Canonical::Decimal(d) => check_decimal_constant(d), - Canonical::Struct(s) => { - let children = s.children(); - if children.is_empty() { - true - } else { - // For struct, check each child recursively. - let first_scalar = s.scalar_at(0)?; - let mut is_const = true; - for i in 1..s.len() { - if s.scalar_at(i)? != first_scalar { - is_const = false; - break; - } - } - is_const - } - } - Canonical::Extension(e) => { - // Extension arrays delegate to their storage. - let first_scalar = e.scalar_at(0)?; - let mut is_const = true; - for i in 1..e.len() { - if e.scalar_at(i)? != first_scalar { - is_const = false; - break; - } - } - is_const - } - Canonical::List(l) => { - let first_scalar = l.scalar_at(0)?; - let mut is_const = true; - for i in 1..l.len() { - if l.scalar_at(i)? != first_scalar { - is_const = false; - break; - } - } - is_const - } - Canonical::FixedSizeList(f) => { - let first_scalar = f.scalar_at(0)?; - let mut is_const = true; - for i in 1..f.len() { - if f.scalar_at(i)? != first_scalar { - is_const = false; - break; - } - } - is_const - } + Canonical::Struct(s) => check_struct_constant(s, ctx)?, + Canonical::Extension(e) => check_extension_constant(e, ctx)?, + Canonical::List(l) => check_listview_constant(l, ctx)?, + Canonical::FixedSizeList(f) => check_fixed_size_list_constant(f, ctx)?, Canonical::Null(_) => true, }; From ab7c96dc4ba720073b62589bdc442ffd31795c8a Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 13:04:37 -0700 Subject: [PATCH 4/9] is-constant Signed-off-by: Nicholas Gates --- .../aggregate_fn/fns/is_constant/extension.rs | 16 ++++++++ .../fns/is_constant/fixed_size_list.rs | 30 ++++++++++++++ .../src/aggregate_fn/fns/is_constant/list.rs | 39 +++++++++++++++++++ .../aggregate_fn/fns/is_constant/struct_.rs | 18 +++++++++ 4 files changed, 103 insertions(+) create mode 100644 vortex-array/src/aggregate_fn/fns/is_constant/extension.rs create mode 100644 vortex-array/src/aggregate_fn/fns/is_constant/fixed_size_list.rs create mode 100644 vortex-array/src/aggregate_fn/fns/is_constant/list.rs create mode 100644 vortex-array/src/aggregate_fn/fns/is_constant/struct_.rs diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/extension.rs b/vortex-array/src/aggregate_fn/fns/is_constant/extension.rs new file mode 100644 index 00000000000..3826e1e0f0e --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/is_constant/extension.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexResult; + +use super::is_constant; +use crate::ExecutionCtx; +use crate::arrays::ExtensionArray; + +/// Check if an extension array is constant by delegating to its storage array. +pub(super) fn check_extension_constant( + e: &ExtensionArray, + ctx: &mut ExecutionCtx, +) -> VortexResult { + is_constant(e.storage_array(), ctx) +} diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/fixed_size_list.rs b/vortex-array/src/aggregate_fn/fns/is_constant/fixed_size_list.rs new file mode 100644 index 00000000000..74467d35672 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/is_constant/fixed_size_list.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexResult; + +use super::arrays_value_equal; +use crate::ExecutionCtx; +use crate::arrays::FixedSizeListArray; + +/// Check if a fixed-size list array is constant by comparing each list's elements. +/// +/// Uses `binary(Operator::Eq)` for element-wise value comparison with null-safe equality. +pub(super) fn check_fixed_size_list_constant( + f: &FixedSizeListArray, + ctx: &mut ExecutionCtx, +) -> VortexResult { + if f.len() <= 1 { + return Ok(true); + } + + let first_elements = f.fixed_size_list_elements_at(0)?; + for i in 1..f.len() { + let current_elements = f.fixed_size_list_elements_at(i)?; + if !arrays_value_equal(&first_elements, ¤t_elements, ctx)? { + return Ok(false); + } + } + + Ok(true) +} diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/list.rs b/vortex-array/src/aggregate_fn/fns/is_constant/list.rs new file mode 100644 index 00000000000..b6455ca7194 --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/is_constant/list.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexResult; + +use super::arrays_value_equal; +use crate::ExecutionCtx; +use crate::arrays::ListViewArray; + +/// Check if a list view array is constant by comparing each list's elements. +/// +/// A list view array is constant if all lists have the same size and the same elements. +/// Uses `binary(Operator::Eq)` for element-wise value comparison with null-safe equality. +pub(super) fn check_listview_constant( + l: &ListViewArray, + ctx: &mut ExecutionCtx, +) -> VortexResult { + if l.len() <= 1 { + return Ok(true); + } + + let first_size = l.size_at(0); + let first_elements = l.list_elements_at(0)?; + + for i in 1..l.len() { + if l.size_at(i) != first_size { + return Ok(false); + } + if first_size == 0 { + continue; + } + let current_elements = l.list_elements_at(i)?; + if !arrays_value_equal(&first_elements, ¤t_elements, ctx)? { + return Ok(false); + } + } + + Ok(true) +} diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/struct_.rs b/vortex-array/src/aggregate_fn/fns/is_constant/struct_.rs new file mode 100644 index 00000000000..590ae60d1fe --- /dev/null +++ b/vortex-array/src/aggregate_fn/fns/is_constant/struct_.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use vortex_error::VortexResult; + +use super::is_constant; +use crate::ExecutionCtx; +use crate::arrays::StructArray; + +/// Check if a struct array is constant by checking each field independently. +pub(super) fn check_struct_constant(s: &StructArray, ctx: &mut ExecutionCtx) -> VortexResult { + for field in s.unmasked_fields().iter() { + if !is_constant(field, ctx)? { + return Ok(false); + } + } + Ok(true) +} From 852ac32249a9c0f1e0932f299a7a5c73cc139eaa Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 13:05:03 -0700 Subject: [PATCH 5/9] is-constant Signed-off-by: Nicholas Gates --- vortex-array/src/aggregate_fn/fns/is_constant/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs index 72aadbdba5c..9599f1429a1 100644 --- a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs @@ -33,7 +33,6 @@ use crate::aggregate_fn::AggregateFnId; use crate::aggregate_fn::AggregateFnVTable; use crate::aggregate_fn::DynAccumulator; use crate::aggregate_fn::EmptyOptions; -use crate::arrays::BoolArray; use crate::arrays::Constant; use crate::arrays::Null; use crate::builtins::ArrayBuiltins; @@ -51,6 +50,8 @@ use crate::scalar_fn::fns::operators::Operator; /// Check if two arrays of the same length have equal values at every position (null-safe). /// /// Two positions are considered equal if they are both null, or both non-null with the same value. +/// +// TODO(ngates): move this function out when we have any/all aggregate functions. fn arrays_value_equal(a: &ArrayRef, b: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { debug_assert_eq!(a.len(), b.len()); if a.is_empty() { From a1039fec2acb6325868e29522ea106232d2b071c Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 13:28:49 -0700 Subject: [PATCH 6/9] is-constant Signed-off-by: Nicholas Gates --- .../datetime-parts/src/compute/is_constant.rs | 26 +--------- .../decimal_byte_parts/compute/is_constant.rs | 25 +--------- .../src/bitpacking/compute/is_constant.rs | 25 +--------- .../fastlanes/src/for/compute/is_constant.rs | 25 +--------- encodings/runend/src/compute/is_constant.rs | 25 +--------- .../src/aggregate_fn/fns/is_constant/mod.rs | 29 ++++++++++++ .../src/arrays/dict/compute/is_constant.rs | 47 +++++++------------ 7 files changed, 50 insertions(+), 152 deletions(-) diff --git a/encodings/datetime-parts/src/compute/is_constant.rs b/encodings/datetime-parts/src/compute/is_constant.rs index a9098461111..5c38c3469a1 100644 --- a/encodings/datetime-parts/src/compute/is_constant.rs +++ b/encodings/datetime-parts/src/compute/is_constant.rs @@ -2,14 +2,11 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors use vortex_array::ArrayRef; -use vortex_array::DynArray; use vortex_array::ExecutionCtx; use vortex_array::aggregate_fn::AggregateFnRef; use vortex_array::aggregate_fn::fns::is_constant::IsConstant; use vortex_array::aggregate_fn::fns::is_constant::is_constant; -use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; use vortex_array::aggregate_fn::kernels::DynAggregateKernel; -use vortex_array::dtype::Nullability; use vortex_array::scalar::Scalar; use vortex_error::VortexResult; @@ -39,27 +36,6 @@ impl DynAggregateKernel for DateTimePartsIsConstantKernel { let result = is_constant(array.days(), ctx)? && is_constant(array.seconds(), ctx)? && is_constant(array.subseconds(), ctx)?; - - let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); - - if result { - let first_value = if batch.is_empty() { - return Ok(Some(Scalar::null(partial_dtype))); - } else { - batch.scalar_at(0)?.into_nullable() - }; - Ok(Some(Scalar::struct_( - partial_dtype, - vec![Scalar::bool(true, Nullability::NonNullable), first_value], - ))) - } else { - Ok(Some(Scalar::struct_( - partial_dtype, - vec![ - Scalar::bool(false, Nullability::NonNullable), - Scalar::null(batch.dtype().as_nullable()), - ], - ))) - } + Ok(Some(IsConstant::make_partial(batch, result)?)) } } diff --git a/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/is_constant.rs b/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/is_constant.rs index b73abb782bc..bca11eda747 100644 --- a/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/is_constant.rs +++ b/encodings/decimal-byte-parts/src/decimal_byte_parts/compute/is_constant.rs @@ -2,14 +2,11 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors use vortex_array::ArrayRef; -use vortex_array::DynArray; use vortex_array::ExecutionCtx; use vortex_array::aggregate_fn::AggregateFnRef; use vortex_array::aggregate_fn::fns::is_constant::IsConstant; use vortex_array::aggregate_fn::fns::is_constant::is_constant; -use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; use vortex_array::aggregate_fn::kernels::DynAggregateKernel; -use vortex_array::dtype::Nullability; use vortex_array::scalar::Scalar; use vortex_error::VortexResult; @@ -37,26 +34,6 @@ impl DynAggregateKernel for DecimalBytePartsIsConstantKernel { }; let result = is_constant(array.msp(), ctx)?; - let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); - - if result { - let first_value = if batch.is_empty() { - return Ok(Some(Scalar::null(partial_dtype))); - } else { - batch.scalar_at(0)?.into_nullable() - }; - Ok(Some(Scalar::struct_( - partial_dtype, - vec![Scalar::bool(true, Nullability::NonNullable), first_value], - ))) - } else { - Ok(Some(Scalar::struct_( - partial_dtype, - vec![ - Scalar::bool(false, Nullability::NonNullable), - Scalar::null(batch.dtype().as_nullable()), - ], - ))) - } + Ok(Some(IsConstant::make_partial(batch, result)?)) } } diff --git a/encodings/fastlanes/src/bitpacking/compute/is_constant.rs b/encodings/fastlanes/src/bitpacking/compute/is_constant.rs index 8c13cf956d5..d3efa37adef 100644 --- a/encodings/fastlanes/src/bitpacking/compute/is_constant.rs +++ b/encodings/fastlanes/src/bitpacking/compute/is_constant.rs @@ -6,18 +6,15 @@ use std::ops::Range; use itertools::Itertools; use lending_iterator::LendingIterator; use vortex_array::ArrayRef; -use vortex_array::DynArray; use vortex_array::ExecutionCtx; use vortex_array::ToCanonical; use vortex_array::aggregate_fn::AggregateFnRef; use vortex_array::aggregate_fn::fns::is_constant::IsConstant; -use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; use vortex_array::aggregate_fn::fns::is_constant::primitive::IS_CONST_LANE_WIDTH; use vortex_array::aggregate_fn::fns::is_constant::primitive::compute_is_constant; use vortex_array::aggregate_fn::kernels::DynAggregateKernel; use vortex_array::arrays::PrimitiveArray; use vortex_array::dtype::IntegerPType; -use vortex_array::dtype::Nullability; use vortex_array::match_each_integer_ptype; use vortex_array::match_each_unsigned_integer_ptype; use vortex_array::scalar::Scalar; @@ -50,27 +47,7 @@ impl DynAggregateKernel for BitPackedIsConstantKernel { bitpacked_is_constant::() }>(array)? }); - let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); - - if result { - let first_value = if batch.is_empty() { - return Ok(Some(Scalar::null(partial_dtype))); - } else { - batch.scalar_at(0)?.into_nullable() - }; - Ok(Some(Scalar::struct_( - partial_dtype, - vec![Scalar::bool(true, Nullability::NonNullable), first_value], - ))) - } else { - Ok(Some(Scalar::struct_( - partial_dtype, - vec![ - Scalar::bool(false, Nullability::NonNullable), - Scalar::null(batch.dtype().as_nullable()), - ], - ))) - } + Ok(Some(IsConstant::make_partial(batch, result)?)) } } diff --git a/encodings/fastlanes/src/for/compute/is_constant.rs b/encodings/fastlanes/src/for/compute/is_constant.rs index d55da25b086..779e9bc900f 100644 --- a/encodings/fastlanes/src/for/compute/is_constant.rs +++ b/encodings/fastlanes/src/for/compute/is_constant.rs @@ -2,14 +2,11 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors use vortex_array::ArrayRef; -use vortex_array::DynArray; use vortex_array::ExecutionCtx; use vortex_array::aggregate_fn::AggregateFnRef; use vortex_array::aggregate_fn::fns::is_constant::IsConstant; use vortex_array::aggregate_fn::fns::is_constant::is_constant; -use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; use vortex_array::aggregate_fn::kernels::DynAggregateKernel; -use vortex_array::dtype::Nullability; use vortex_array::scalar::Scalar; use vortex_error::VortexResult; @@ -37,26 +34,6 @@ impl DynAggregateKernel for FoRIsConstantKernel { }; let result = is_constant(array.encoded(), ctx)?; - let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); - - if result { - let first_value = if batch.is_empty() { - return Ok(Some(Scalar::null(partial_dtype))); - } else { - batch.scalar_at(0)?.into_nullable() - }; - Ok(Some(Scalar::struct_( - partial_dtype, - vec![Scalar::bool(true, Nullability::NonNullable), first_value], - ))) - } else { - Ok(Some(Scalar::struct_( - partial_dtype, - vec![ - Scalar::bool(false, Nullability::NonNullable), - Scalar::null(batch.dtype().as_nullable()), - ], - ))) - } + Ok(Some(IsConstant::make_partial(batch, result)?)) } } diff --git a/encodings/runend/src/compute/is_constant.rs b/encodings/runend/src/compute/is_constant.rs index 547f387731c..89e8711b6eb 100644 --- a/encodings/runend/src/compute/is_constant.rs +++ b/encodings/runend/src/compute/is_constant.rs @@ -2,14 +2,11 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors use vortex_array::ArrayRef; -use vortex_array::DynArray; use vortex_array::ExecutionCtx; use vortex_array::aggregate_fn::AggregateFnRef; use vortex_array::aggregate_fn::fns::is_constant::IsConstant; use vortex_array::aggregate_fn::fns::is_constant::is_constant; -use vortex_array::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype; use vortex_array::aggregate_fn::kernels::DynAggregateKernel; -use vortex_array::dtype::Nullability; use vortex_array::scalar::Scalar; use vortex_error::VortexResult; @@ -37,26 +34,6 @@ impl DynAggregateKernel for RunEndIsConstantKernel { }; let result = is_constant(array.values(), ctx)?; - let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); - - if result { - let first_value = if batch.is_empty() { - return Ok(Some(Scalar::null(partial_dtype))); - } else { - batch.scalar_at(0)?.into_nullable() - }; - Ok(Some(Scalar::struct_( - partial_dtype, - vec![Scalar::bool(true, Nullability::NonNullable), first_value], - ))) - } else { - Ok(Some(Scalar::struct_( - partial_dtype, - vec![ - Scalar::bool(false, Nullability::NonNullable), - Scalar::null(batch.dtype().as_nullable()), - ], - ))) - } + Ok(Some(IsConstant::make_partial(batch, result)?)) } } diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs index 9599f1429a1..57b75e3fc54 100644 --- a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs @@ -180,6 +180,35 @@ pub fn is_constant(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult VortexResult { + let partial_dtype = make_is_constant_partial_dtype(batch.dtype()); + if is_constant { + if batch.is_empty() { + return Ok(Scalar::null(partial_dtype)); + } + let first_value = batch.scalar_at(0)?.into_nullable(); + Ok(Scalar::struct_( + partial_dtype, + vec![Scalar::bool(true, Nullability::NonNullable), first_value], + )) + } else { + Ok(Scalar::struct_( + partial_dtype, + vec![ + Scalar::bool(false, Nullability::NonNullable), + Scalar::null(batch.dtype().as_nullable()), + ], + )) + } + } +} + /// Partial accumulator state for is_constant. pub struct IsConstantPartial { is_constant: bool, diff --git a/vortex-array/src/arrays/dict/compute/is_constant.rs b/vortex-array/src/arrays/dict/compute/is_constant.rs index 739986c5aba..302af0f11ba 100644 --- a/vortex-array/src/arrays/dict/compute/is_constant.rs +++ b/vortex-array/src/arrays/dict/compute/is_constant.rs @@ -2,9 +2,9 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors use vortex_error::VortexResult; +use vortex_mask::Mask; use crate::ArrayRef; -use crate::DynArray; use crate::ExecutionCtx; use crate::aggregate_fn::AggregateFnRef; use crate::aggregate_fn::fns::is_constant::IsConstant; @@ -16,7 +16,8 @@ use crate::scalar::Scalar; /// Dict-specific is_constant kernel. /// /// If codes are constant, the whole array is constant. -/// Otherwise, check the values array. +/// When all dictionary values are referenced, is_constant can be computed directly on the values +/// array. Otherwise, unreferenced values are filtered out first. #[derive(Debug)] pub(crate) struct DictIsConstantKernel; @@ -35,37 +36,21 @@ impl DynAggregateKernel for DictIsConstantKernel { return Ok(None); }; - let result = if is_constant(dict.codes(), ctx)? { - true - } else { + // If codes are constant, only one dictionary value is referenced → constant. + if is_constant(dict.codes(), ctx)? { + return Ok(Some(IsConstant::make_partial(batch, true)?)); + } + + // Otherwise, check the values array. Filter to only referenced values if needed. + let result = if dict.has_all_values_referenced() { is_constant(dict.values(), ctx)? + } else { + let referenced_mask = dict.compute_referenced_values_mask(true)?; + let mask = Mask::from(referenced_mask); + let filtered_values = dict.values().filter(mask)?; + is_constant(&filtered_values, ctx)? }; - // Return in the partial dtype format: struct {is_constant, value} - // We use the first scalar as the representative value. - let partial_dtype = - crate::aggregate_fn::fns::is_constant::make_is_constant_partial_dtype(batch.dtype()); - if result { - let first_value = if batch.is_empty() { - return Ok(Some(Scalar::null(partial_dtype))); - } else { - batch.scalar_at(0)?.into_nullable() - }; - Ok(Some(Scalar::struct_( - partial_dtype, - vec![ - Scalar::bool(true, crate::dtype::Nullability::NonNullable), - first_value, - ], - ))) - } else { - Ok(Some(Scalar::struct_( - partial_dtype, - vec![ - Scalar::bool(false, crate::dtype::Nullability::NonNullable), - Scalar::null(batch.dtype().as_nullable()), - ], - ))) - } + Ok(Some(IsConstant::make_partial(batch, result)?)) } } From 90b99520e108196f096f4b5d4f1bbc8114f2dd91 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 13:29:16 -0700 Subject: [PATCH 7/9] is-constant Signed-off-by: Nicholas Gates --- vortex-array/public-api.lock | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vortex-array/public-api.lock b/vortex-array/public-api.lock index e9911df785d..5f37ffc6887 100644 --- a/vortex-array/public-api.lock +++ b/vortex-array/public-api.lock @@ -40,6 +40,10 @@ pub fn vortex_array::aggregate_fn::fns::is_constant::primitive::compute_is_const pub struct vortex_array::aggregate_fn::fns::is_constant::IsConstant +impl vortex_array::aggregate_fn::fns::is_constant::IsConstant + +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::make_partial(batch: &vortex_array::ArrayRef, is_constant: bool) -> vortex_error::VortexResult + impl core::clone::Clone for vortex_array::aggregate_fn::fns::is_constant::IsConstant pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::clone(&self) -> vortex_array::aggregate_fn::fns::is_constant::IsConstant @@ -54,7 +58,7 @@ pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Options = vor pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Partial = vortex_array::aggregate_fn::fns::is_constant::IsConstantPartial -pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::accumulate(&self, partial: &mut Self::Partial, batch: &vortex_array::Columnar, _ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::accumulate(&self, partial: &mut Self::Partial, batch: &vortex_array::Columnar, ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::coerce_args(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult @@ -506,7 +510,7 @@ pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Options = vor pub type vortex_array::aggregate_fn::fns::is_constant::IsConstant::Partial = vortex_array::aggregate_fn::fns::is_constant::IsConstantPartial -pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::accumulate(&self, partial: &mut Self::Partial, batch: &vortex_array::Columnar, _ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> +pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::accumulate(&self, partial: &mut Self::Partial, batch: &vortex_array::Columnar, ctx: &mut vortex_array::ExecutionCtx) -> vortex_error::VortexResult<()> pub fn vortex_array::aggregate_fn::fns::is_constant::IsConstant::coerce_args(&self, options: &Self::Options, input_dtype: &vortex_array::dtype::DType) -> vortex_error::VortexResult From 1dd80f7fc28cbea3ffa9d37658c50dd395acea61 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 13:35:03 -0700 Subject: [PATCH 8/9] is-constant Signed-off-by: Nicholas Gates --- vortex-array/src/aggregate_fn/fns/is_constant/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs index 57b75e3fc54..b1bb48d1a85 100644 --- a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs @@ -27,7 +27,6 @@ use crate::Columnar; use crate::DynArray; use crate::ExecutionCtx; use crate::IntoArray; -use crate::ToCanonical; use crate::aggregate_fn::Accumulator; use crate::aggregate_fn::AggregateFnId; use crate::aggregate_fn::AggregateFnVTable; From 360fe1491a7ba2e9cd5e05445ecc08d21dc6dd63 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Wed, 18 Mar 2026 17:21:56 -0700 Subject: [PATCH 9/9] IsSortedKernel Signed-off-by: Nicholas Gates --- vortex-array/src/aggregate_fn/fns/is_constant/mod.rs | 9 +++++---- vortex-array/src/compute/is_constant.rs | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs index b1bb48d1a85..d71cfd19d91 100644 --- a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs @@ -10,6 +10,7 @@ pub mod primitive; mod struct_; mod varbin; +use vortex_error::VortexExpect; use vortex_error::VortexResult; use vortex_mask::Mask; @@ -234,9 +235,10 @@ impl IsConstantPartial { } } +static NAMES: std::sync::LazyLock = + std::sync::LazyLock::new(|| FieldNames::from(["is_constant", "value"])); + pub fn make_is_constant_partial_dtype(element_dtype: &DType) -> DType { - static NAMES: std::sync::LazyLock = - std::sync::LazyLock::new(|| FieldNames::from(["is_constant", "value"])); DType::Struct( StructFields::new( NAMES.clone(), @@ -409,8 +411,7 @@ impl AggregateFnVTable for IsConstant { } fn finalize(&self, partials: ArrayRef) -> VortexResult { - // TODO: extract is_constant field from struct array - Ok(partials) + partials.get_item(NAMES.get(0).vortex_expect("out of bounds").clone()) } fn finalize_scalar(&self, partial: Scalar) -> VortexResult { diff --git a/vortex-array/src/compute/is_constant.rs b/vortex-array/src/compute/is_constant.rs index 1666c4bad2b..64b545439c5 100644 --- a/vortex-array/src/compute/is_constant.rs +++ b/vortex-array/src/compute/is_constant.rs @@ -8,6 +8,7 @@ use vortex_error::VortexResult; use crate::ArrayRef; use crate::LEGACY_SESSION; use crate::VortexSessionExecute; +use crate::compute::Options; /// Computes whether an array has constant values. /// @@ -59,7 +60,7 @@ impl Default for IsConstantOpts { } } -impl super::Options for IsConstantOpts { +impl Options for IsConstantOpts { fn as_any(&self) -> &dyn Any { self }