From d727454ecf8b719665c486aa5d595f7785973bf5 Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Tue, 29 Sep 2026 10:29:02 +0100 Subject: [PATCH 1/4] deprecate null_as_false Signed-off-by: Mikhail Kot --- .../src/aggregate_fn/fns/is_constant/mod.rs | 3 +- vortex-array/src/arrays/bool/compute/zip.rs | 3 +- .../src/arrays/listview/compute/zip.rs | 2 +- .../src/arrays/primitive/compute/zip.rs | 3 +- .../src/arrays/struct_/compute/zip.rs | 3 +- .../src/arrays/varbinview/compute/zip.rs | 3 +- vortex-array/src/mask.rs | 81 ++----------------- vortex-cuda/src/layout.rs | 5 +- vortex-layout/src/layouts/dict/reader.rs | 3 +- vortex-layout/src/layouts/flat/reader.rs | 5 +- vortex-layout/src/layouts/list/reader.rs | 10 +-- vortex-layout/src/layouts/partitioned.rs | 6 +- vortex-layout/src/layouts/row_idx/mod.rs | 5 +- vortex-layout/src/layouts/zoned/zone_map.rs | 5 +- vortex-spatial/src/prune/distance.rs | 2 - 15 files changed, 39 insertions(+), 100 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 588134a1961..c625f033a10 100644 --- a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs @@ -14,6 +14,7 @@ mod varbin; use vortex_error::VortexExpect; use vortex_error::VortexResult; use vortex_error::vortex_bail; +use vortex_mask::Mask; use vortex_session::registry::CachedId; use self::bool::check_bool_constant; @@ -76,7 +77,7 @@ fn arrays_value_equal(a: &ArrayRef, b: &ArrayRef, ctx: &mut ExecutionCtx) -> Vor // 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.null_as_false().execute(ctx)?; + let eq_result = eq_result.fill_null(false)?.execute::(ctx)?; Ok(eq_result.true_count() == valid_count) } diff --git a/vortex-array/src/arrays/bool/compute/zip.rs b/vortex-array/src/arrays/bool/compute/zip.rs index ad311572395..9eaecb2fb39 100644 --- a/vortex-array/src/arrays/bool/compute/zip.rs +++ b/vortex-array/src/arrays/bool/compute/zip.rs @@ -13,6 +13,7 @@ use crate::array::ArrayView; use crate::arrays::Bool; use crate::arrays::BoolArray; use crate::arrays::bool::BoolArrayExt; +use crate::builtins::ArrayBuiltins; use crate::scalar_fn::fns::zip::ZipKernel; use crate::scalar_fn::fns::zip::zip_validity; @@ -34,7 +35,7 @@ impl ZipKernel for Bool { }; // Null mask entries select `if_false`, matching `Zip`'s SQL ELSE semantics. - let mask = mask.clone().null_as_false().execute(ctx)?; + let mask = mask.clone().fill_null(false)?.execute::(ctx)?; let mask_values = match &mask { // Defer trivial masks to the generic zip, which just casts the surviving side. Mask::AllTrue(_) | Mask::AllFalse(_) => return Ok(None), diff --git a/vortex-array/src/arrays/listview/compute/zip.rs b/vortex-array/src/arrays/listview/compute/zip.rs index d5e2a6bf406..6ab789abe0e 100644 --- a/vortex-array/src/arrays/listview/compute/zip.rs +++ b/vortex-array/src/arrays/listview/compute/zip.rs @@ -50,7 +50,7 @@ impl ZipKernel for ListView { }; // Null mask entries select `if_false`, matching `Zip`'s SQL ELSE semantics. - let mask = mask.clone().null_as_false().execute(ctx)?; + let mask = mask.clone().fill_null(false)?.execute::(ctx)?; match &mask { // Defer the trivial masks to the generic zip, which just casts one side. Mask::AllTrue(_) | Mask::AllFalse(_) => return Ok(None), diff --git a/vortex-array/src/arrays/primitive/compute/zip.rs b/vortex-array/src/arrays/primitive/compute/zip.rs index 35e49831acc..cd48bcddbc6 100644 --- a/vortex-array/src/arrays/primitive/compute/zip.rs +++ b/vortex-array/src/arrays/primitive/compute/zip.rs @@ -15,6 +15,7 @@ use crate::IntoArray; use crate::array::ArrayView; use crate::arrays::Primitive; use crate::arrays::PrimitiveArray; +use crate::builtins::ArrayBuiltins; use crate::dtype::NativePType; use crate::match_each_native_ptype; use crate::scalar_fn::fns::zip::ZipKernel; @@ -46,7 +47,7 @@ impl ZipKernel for Primitive { } // Null mask entries select `if_false`, matching `Zip`'s SQL ELSE semantics. - let mask = mask.clone().null_as_false().execute(ctx)?; + let mask = mask.clone().fill_null(false)?.execute::(ctx)?; match &mask { // Defer trivial masks to the generic zip, which just casts the surviving side. Mask::AllTrue(_) | Mask::AllFalse(_) => return Ok(None), diff --git a/vortex-array/src/arrays/struct_/compute/zip.rs b/vortex-array/src/arrays/struct_/compute/zip.rs index 394cc5c8424..a1da2c23f82 100644 --- a/vortex-array/src/arrays/struct_/compute/zip.rs +++ b/vortex-array/src/arrays/struct_/compute/zip.rs @@ -6,6 +6,7 @@ use std::ops::BitOr; use std::ops::Not; use vortex_error::VortexResult; +use vortex_mask::Mask; use crate::ArrayRef; use crate::ExecutionCtx; @@ -48,7 +49,7 @@ impl ZipKernel for Struct { (Validity::AllInvalid, Validity::AllInvalid) => Validity::AllInvalid, (v1, v2) => { - let mask_mask = mask.clone().null_as_false().execute(ctx)?; + let mask_mask = mask.clone().fill_null(false)?.execute::(ctx)?; let v1m = v1.execute_mask(if_true.len(), ctx)?; let v2m = v2.execute_mask(if_false.len(), ctx)?; diff --git a/vortex-array/src/arrays/varbinview/compute/zip.rs b/vortex-array/src/arrays/varbinview/compute/zip.rs index 2711a7e4fc6..e4a76a30f5b 100644 --- a/vortex-array/src/arrays/varbinview/compute/zip.rs +++ b/vortex-array/src/arrays/varbinview/compute/zip.rs @@ -19,6 +19,7 @@ use crate::arrays::varbinview::BinaryView; use crate::arrays::varbinview::VarBinViewArrayExt; use crate::builders::DeduplicatedBuffers; use crate::builders::LazyBitBufferBuilder; +use crate::builtins::ArrayBuiltins; use crate::scalar_fn::fns::zip::ZipKernel; // A dedicated VarBinView zip kernel that builds the result directly by adjusting views and validity, @@ -57,7 +58,7 @@ impl ZipKernel for VarBinView { let true_validity = if_true.varbinview_validity().execute_mask(len, ctx)?; let false_validity = if_false.varbinview_validity().execute_mask(len, ctx)?; - let mask = mask.clone().null_as_false().execute(ctx)?; + let mask = mask.clone().fill_null(false)?.execute::(ctx)?; let if_false_view = if_false; match mask.slices() { AllOr::All => push_range( diff --git a/vortex-array/src/mask.rs b/vortex-array/src/mask.rs index e3985da2272..07457fc014b 100644 --- a/vortex-array/src/mask.rs +++ b/vortex-array/src/mask.rs @@ -10,10 +10,10 @@ use crate::Executable; use crate::ExecutionCtx; use crate::IntoArray; use crate::arrays::BoolArray; +use crate::builtins::ArrayBuiltins; use crate::columnar::Columnar; use crate::dtype::DType; use crate::dtype::Nullability; -use crate::validity::Validity; impl Executable for Mask { /// Executes a boolean array into a [`Mask`]. @@ -42,53 +42,20 @@ impl Executable for Mask { } } -/// An adapter that coerces null elements of a boolean array to `false` before executing it into a -/// [`Mask`]. Created by [`ArrayRef::null_as_false`]. -/// -/// Use for filter and pruning predicates over nullable data, where SQL semantics treat `NULL` as -/// not matching. +#[deprecated = "Use array.fill_null(false)?.execute::(ctx)"] pub struct NullAsFalse(ArrayRef); - impl ArrayRef { - /// Returns an adapter that treats null elements of this boolean array as `false` when executed - /// into a [`Mask`]. See [`NullAsFalse`]. + #[deprecated = "Use array.fill_null(false)?.execute::(ctx)"] + #[allow(deprecated)] pub fn null_as_false(self) -> NullAsFalse { NullAsFalse(self) } } - +#[allow(deprecated)] impl NullAsFalse { - /// Executes the boolean array into a [`Mask`], coercing null elements to `false`. - /// - /// Canonicalizes the (possibly lazy) array exactly once and folds validity into the value bits - /// with a single `AND` that reuses the value buffer when it is uniquely owned. + #[deprecated = "Use array.fill_null(false)?.execute::(ctx)"] pub fn execute(self, ctx: &mut ExecutionCtx) -> VortexResult { - let array = self.0; - if !matches!(array.dtype(), DType::Bool(_)) { - vortex_bail!("Mask array must have boolean dtype, not {}", array.dtype()); - } - // Non-nullable input needs no coercion; defer to the strict `Mask` execution. - if !array.dtype().is_nullable() { - return array.execute::(ctx); - } - - let len = array.len(); - Ok(match array.execute::(ctx)? { - Columnar::Constant(c) => Mask::new(len, c.scalar().as_bool().value().unwrap_or(false)), - Columnar::Canonical(c) => { - let bool = c.into_array().execute::(ctx)?; - match bool.as_ref().validity()? { - Validity::NonNullable | Validity::AllValid => { - Mask::from_buffer(bool.into_bit_buffer()) - } - Validity::AllInvalid => Mask::new_false(len), - Validity::Array(v) => { - let validity_bits = v.execute::(ctx)?.into_bit_buffer(); - Mask::from_buffer(bool.into_bit_buffer() & &validity_bits) - } - } - } - }) + self.0.fill_null(false)?.execute::(ctx) } } @@ -143,38 +110,4 @@ mod tests { Ok(()) } - #[test] - fn null_as_false_non_nullable() -> VortexResult<()> { - let array = BoolArray::from_iter([true, false, true]).into_array(); - let mask = array.null_as_false().execute(&mut ctx())?; - assert_eq!(mask, Mask::from_iter([true, false, true])); - Ok(()) - } - - #[test] - fn null_as_false_treats_null_as_false() -> VortexResult<()> { - let array = BoolArray::from_iter([Some(true), None, Some(false), None]).into_array(); - let mask = array.null_as_false().execute(&mut ctx())?; - assert_eq!(mask, Mask::from_iter([true, false, false, false])); - Ok(()) - } - - #[test] - fn null_as_false_null_constant() -> VortexResult<()> { - let array = - ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), 4).into_array(); - let mask = array.null_as_false().execute(&mut ctx())?; - assert_eq!(mask, Mask::new_false(4)); - Ok(()) - } - - #[test] - fn null_as_false_matches_fill_null_then_mask() -> VortexResult<()> { - let array = - BoolArray::from_iter([Some(true), None, Some(false), Some(true), None]).into_array(); - let via_fill_null = array.fill_null(false)?.execute::(&mut ctx())?; - let via_coerce = array.null_as_false().execute(&mut ctx())?; - assert_eq!(via_coerce, via_fill_null); - Ok(()) - } } diff --git a/vortex-cuda/src/layout.rs b/vortex-cuda/src/layout.rs index f30e152017b..a8128963eee 100644 --- a/vortex-cuda/src/layout.rs +++ b/vortex-cuda/src/layout.rs @@ -20,6 +20,7 @@ use vortex::array::MaskFuture; use vortex::array::ProstMetadata; use vortex::array::VortexSessionExecute; use vortex::array::arrays::Constant; +use vortex::array::builtins::ArrayBuiltins; use vortex::array::expr::BoundExpression; use vortex::array::expr::stats::Precision; use vortex::array::expr::stats::Stat; @@ -314,12 +315,12 @@ impl LayoutReader for CudaFlatReader { let array = array.apply_bound(&expr)?; let array = array.filter(mask.clone())?; let mut ctx = session.create_execution_ctx(); - let array_mask = array.null_as_false().execute(&mut ctx)?; + let array_mask = array.fill_null(false)?.execute::(&mut ctx)?; mask.intersect_by_rank(&array_mask) } else { let array = array.apply_bound(&expr)?; let mut ctx = session.create_execution_ctx(); - let array_mask = array.null_as_false().execute(&mut ctx)?; + let array_mask = array.fill_null(false)?.execute::(&mut ctx)?; mask.bitand(&array_mask) }; diff --git a/vortex-layout/src/layouts/dict/reader.rs b/vortex-layout/src/layouts/dict/reader.rs index 493856ad01c..651413b1a46 100644 --- a/vortex-layout/src/layouts/dict/reader.rs +++ b/vortex-layout/src/layouts/dict/reader.rs @@ -16,6 +16,7 @@ use vortex_array::MaskFuture; use vortex_array::VortexSessionExecute; use vortex_array::arrays::DictArray; use vortex_array::arrays::SharedArray; +use vortex_array::builtins::ArrayBuiltins; use vortex_array::dtype::DType; use vortex_array::dtype::FieldMask; use vortex_array::dtype::Nullability; @@ -277,7 +278,7 @@ impl LayoutReader for DictReader { let mask = mask.await?; let mut ctx = session.create_execution_ctx(); - let dict_mask = values.take(codes)?.null_as_false().execute(&mut ctx)?; + let dict_mask = values.take(codes)?.fill_null(false)?.execute::(&mut ctx)?; Ok(mask.bitand(&dict_mask)) })) diff --git a/vortex-layout/src/layouts/flat/reader.rs b/vortex-layout/src/layouts/flat/reader.rs index aa7609f1659..66fee067876 100644 --- a/vortex-layout/src/layouts/flat/reader.rs +++ b/vortex-layout/src/layouts/flat/reader.rs @@ -11,6 +11,7 @@ use tracing::trace; use vortex_array::ArrayRef; use vortex_array::MaskFuture; use vortex_array::VortexSessionExecute; +use vortex_array::builtins::ArrayBuiltins; use vortex_array::dtype::DType; use vortex_array::dtype::FieldMask; use vortex_array::expr::BoundExpression; @@ -156,14 +157,14 @@ impl LayoutReader for FlatReader { let array = array.apply_bound(&expr)?; let array = array.filter(mask.clone())?; let mut ctx = session.create_execution_ctx(); - let array_mask = array.null_as_false().execute(&mut ctx)?; + let array_mask = array.fill_null(false)?.execute::(&mut ctx)?; mask.intersect_by_rank(&array_mask) } else { // Run over the full array, with a simpler bitand at the end. let array = array.apply_bound(&expr)?; let mut ctx = session.create_execution_ctx(); - let array_mask = array.null_as_false().execute(&mut ctx)?; + let array_mask = array.fill_null(false)?.execute::(&mut ctx)?; mask.bitand(&array_mask) }; diff --git a/vortex-layout/src/layouts/list/reader.rs b/vortex-layout/src/layouts/list/reader.rs index 53227635de6..b1a3f58c81f 100644 --- a/vortex-layout/src/layouts/list/reader.rs +++ b/vortex-layout/src/layouts/list/reader.rs @@ -437,17 +437,18 @@ impl LayoutReader for ListReader { return Ok(mask); } + let mut ctx = session.create_execution_ctx(); if mask.density() < EXPR_EVAL_THRESHOLD { let predicate = reader .projection_evaluation(&row_range, &expr, MaskFuture::ready(mask.clone()))? .await?; - let predicate_mask = predicate_array_to_mask(predicate, &session)?; + let predicate_mask = predicate.fill_null(false)?.execute::(&mut ctx)?; Ok(mask.intersect_by_rank(&predicate_mask)) } else { let predicate = reader .projection_evaluation(&row_range, &expr, MaskFuture::new_true(len))? .await?; - let predicate_mask = predicate_array_to_mask(predicate, &session)?; + let predicate_mask = predicate.fill_null(false)?.execute::(&mut ctx)?; Ok(mask & &predicate_mask) } })) @@ -599,11 +600,6 @@ fn apply_lengths_validity( } } -fn predicate_array_to_mask(array: ArrayRef, session: &VortexSession) -> VortexResult { - let mut ctx = session.create_execution_ctx(); - array.null_as_false().execute(&mut ctx) -} - #[cfg(test)] mod tests { use std::ops::Range; diff --git a/vortex-layout/src/layouts/partitioned.rs b/vortex-layout/src/layouts/partitioned.rs index 94d1c3f782d..d9d99722f0b 100644 --- a/vortex-layout/src/layouts/partitioned.rs +++ b/vortex-layout/src/layouts/partitioned.rs @@ -11,6 +11,7 @@ use vortex_array::IntoArray; use vortex_array::MaskFuture; use vortex_array::VortexSessionExecute; use vortex_array::arrays::StructArray; +use vortex_array::builtins::ArrayBuiltins; use vortex_array::dtype::DType; use vortex_array::dtype::Nullability; use vortex_array::expr::BoundExpression; @@ -18,6 +19,7 @@ use vortex_array::expr::transform::BoundPartitionedExpr; use vortex_array::validity::Validity; use vortex_error::VortexError; use vortex_error::VortexResult; +use vortex_mask::Mask; use vortex_session::VortexSession; use crate::ArrayFuture; @@ -93,8 +95,8 @@ impl BoundPartitionedExprEval

for BoundPartitionedE let mut ctx = session.create_execution_ctx(); let root_mask = root_scope .apply_bound(&self.root)? - .null_as_false() - .execute(&mut ctx)?; + .fill_null(false)? + .execute::(&mut ctx)?; let mask = mask.bitand(&root_mask); diff --git a/vortex-layout/src/layouts/row_idx/mod.rs b/vortex-layout/src/layouts/row_idx/mod.rs index 7ecf87b5bb2..7b9d9e4fb5c 100644 --- a/vortex-layout/src/layouts/row_idx/mod.rs +++ b/vortex-layout/src/layouts/row_idx/mod.rs @@ -18,6 +18,7 @@ use vortex_array::ArrayRef; use vortex_array::IntoArray; use vortex_array::MaskFuture; use vortex_array::VortexSessionExecute; +use vortex_array::builtins::ArrayBuiltins; use vortex_array::dtype::DType; use vortex_array::dtype::FieldMask; use vortex_array::dtype::FieldName; @@ -319,8 +320,8 @@ fn row_idx_mask_future( let mut ctx = session.create_execution_ctx(); let result_mask = array .apply_bound(&expr)? - .null_as_false() - .execute(&mut ctx)?; + .fill_null(false)? + .execute::(&mut ctx)?; Ok(result_mask.bitand(&mask.await?)) }) diff --git a/vortex-layout/src/layouts/zoned/zone_map.rs b/vortex-layout/src/layouts/zoned/zone_map.rs index 076131d6246..c9e49b0b545 100644 --- a/vortex-layout/src/layouts/zoned/zone_map.rs +++ b/vortex-layout/src/layouts/zoned/zone_map.rs @@ -9,6 +9,7 @@ use vortex_array::ArrayRef; use vortex_array::IntoArray; use vortex_array::VortexSessionExecute; use vortex_array::aggregate_fn::AggregateFnRef; +use vortex_array::builtins::ArrayBuiltins; use vortex_array::aggregate_fn::AggregateFnSatisfaction; use vortex_array::aggregate_fn::fns::all_nan::AllNan; use vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan; @@ -151,12 +152,12 @@ impl ZoneMap { let applied = array.apply_bound(&predicate)?; if !contains_row_count(&applied) { - return applied.null_as_false().execute(&mut ctx); + return applied.fill_null(false)?.execute::(&mut ctx); } let row_count_array = row_count_array(self.zone_len, self.row_count, num_zones)?; let substituted = substitute_row_count(applied, &row_count_array)?; - substituted.null_as_false().execute(&mut ctx) + substituted.fill_null(false)?.execute::(&mut ctx) } fn lower_stats(&self, predicate: BoundExpression) -> VortexResult { diff --git a/vortex-spatial/src/prune/distance.rs b/vortex-spatial/src/prune/distance.rs index e1648b405cc..b453d8cacad 100644 --- a/vortex-spatial/src/prune/distance.rs +++ b/vortex-spatial/src/prune/distance.rs @@ -378,8 +378,6 @@ mod tests { Ok(()) } - /// Backward compat: a zone map written without the `GeometryAabb` stat (an older file) keeps - /// every zone, the missing stat binds to null and `null_as_false` retains the zone. #[test] fn missing_aabb_stat_keeps_all_zones() -> VortexResult<()> { let session = spatial_session(); From 5f090a4cd65c73fbf00a4f2246e126e00bbb78a6 Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Tue, 29 Sep 2026 11:33:36 +0100 Subject: [PATCH 2/4] directly create FillNull Signed-off-by: Mikhail Kot --- vortex-array/src/scalar_fn/fns/fill_null/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vortex-array/src/scalar_fn/fns/fill_null/mod.rs b/vortex-array/src/scalar_fn/fns/fill_null/mod.rs index 8299360bcf9..c67e4048633 100644 --- a/vortex-array/src/scalar_fn/fns/fill_null/mod.rs +++ b/vortex-array/src/scalar_fn/fns/fill_null/mod.rs @@ -16,11 +16,11 @@ use crate::ArrayRef; use crate::CanonicalView; use crate::ColumnarView; use crate::ExecutionCtx; +use crate::IntoArray; use crate::arrays::Bool; use crate::arrays::Decimal; use crate::arrays::Primitive; use crate::arrays::ScalarFnArray; -use crate::builtins::ArrayBuiltins; use crate::dtype::DType; use crate::expr::BoundExpression; use crate::scalar::Scalar; @@ -114,7 +114,8 @@ impl ScalarFnVTable for FillNull { ); let Some(columnar) = input.as_opt::() else { - return input.execute::(ctx)?.fill_null(fill_scalar); + let input = input.execute::(ctx)?; + return Ok(FillNull::try_new(input, fill_value)?.into_array()); }; match columnar { From 2b81987cc8114c799d893daef93d430be965fdea Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Tue, 29 Sep 2026 11:44:20 +0100 Subject: [PATCH 3/4] execute to columnar for zone map pruning Signed-off-by: Mikhail Kot --- vortex-array/src/mask.rs | 1 - vortex-layout/src/layouts/dict/reader.rs | 5 ++- vortex-layout/src/layouts/zoned/zone_map.rs | 34 +++++++++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/vortex-array/src/mask.rs b/vortex-array/src/mask.rs index 07457fc014b..f7cee617ccb 100644 --- a/vortex-array/src/mask.rs +++ b/vortex-array/src/mask.rs @@ -109,5 +109,4 @@ mod tests { assert_eq!(mask, Mask::new_false(4)); Ok(()) } - } diff --git a/vortex-layout/src/layouts/dict/reader.rs b/vortex-layout/src/layouts/dict/reader.rs index 651413b1a46..134c9fc0c1a 100644 --- a/vortex-layout/src/layouts/dict/reader.rs +++ b/vortex-layout/src/layouts/dict/reader.rs @@ -278,7 +278,10 @@ impl LayoutReader for DictReader { let mask = mask.await?; let mut ctx = session.create_execution_ctx(); - let dict_mask = values.take(codes)?.fill_null(false)?.execute::(&mut ctx)?; + let dict_mask = values + .take(codes)? + .fill_null(false)? + .execute::(&mut ctx)?; Ok(mask.bitand(&dict_mask)) })) diff --git a/vortex-layout/src/layouts/zoned/zone_map.rs b/vortex-layout/src/layouts/zoned/zone_map.rs index c9e49b0b545..b2d5719addb 100644 --- a/vortex-layout/src/layouts/zoned/zone_map.rs +++ b/vortex-layout/src/layouts/zoned/zone_map.rs @@ -6,10 +6,11 @@ use std::sync::Arc; use vortex_array::ArrayRef; +use vortex_array::Columnar; +use vortex_array::ExecutionCtx; use vortex_array::IntoArray; use vortex_array::VortexSessionExecute; use vortex_array::aggregate_fn::AggregateFnRef; -use vortex_array::builtins::ArrayBuiltins; use vortex_array::aggregate_fn::AggregateFnSatisfaction; use vortex_array::aggregate_fn::fns::all_nan::AllNan; use vortex_array::aggregate_fn::fns::all_non_nan::AllNonNan; @@ -17,6 +18,7 @@ use vortex_array::aggregate_fn::fns::all_non_null::AllNonNull; use vortex_array::aggregate_fn::fns::all_null::AllNull; use vortex_array::aggregate_fn::fns::bounded_max::BOUNDED_MAX_BOUND; use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax; +use vortex_array::arrays::BoolArray; use vortex_array::arrays::ConstantArray; use vortex_array::arrays::PrimitiveArray; use vortex_array::arrays::StructArray; @@ -152,12 +154,12 @@ impl ZoneMap { let applied = array.apply_bound(&predicate)?; if !contains_row_count(&applied) { - return applied.fill_null(false)?.execute::(&mut ctx); + return null_as_false_mask(applied, &mut ctx); } let row_count_array = row_count_array(self.zone_len, self.row_count, num_zones)?; let substituted = substitute_row_count(applied, &row_count_array)?; - substituted.fill_null(false)?.execute::(&mut ctx) + null_as_false_mask(substituted, &mut ctx) } fn lower_stats(&self, predicate: BoundExpression) -> VortexResult { @@ -166,6 +168,32 @@ impl ZoneMap { } } +/// This is equivalent to fill_null(false)?.execute::(ctx). +/// However, prune work is very fast, and fill_null + optimize calls +/// dominate the runtime. +fn null_as_false_mask(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { + if !array.dtype().is_nullable() { + return array.execute::(ctx); + } + let len = array.len(); + Ok(match array.execute::(ctx)? { + Columnar::Constant(c) => Mask::new(len, c.scalar().as_bool().value().unwrap_or(false)), + Columnar::Canonical(c) => { + let bool = c.into_array().execute::(ctx)?; + match bool.as_ref().validity()? { + Validity::NonNullable | Validity::AllValid => { + Mask::from_buffer(bool.into_bit_buffer()) + } + Validity::AllInvalid => Mask::new_false(len), + Validity::Array(v) => { + let validity_bits = v.execute::(ctx)?.into_bit_buffer(); + Mask::from_buffer(bool.into_bit_buffer() & &validity_bits) + } + } + } + }) +} + struct ZoneMapStatsBinder<'a> { zone_map: &'a ZoneMap, } From ad62289313486ea5482cfbbf4c124613b787e774 Mon Sep 17 00:00:00 2001 From: Mikhail Kot Date: Wed, 7 Oct 2026 12:03:25 +0100 Subject: [PATCH 4/4] fix Signed-off-by: Mikhail Kot --- vortex-layout/src/layouts/zoned/zone_map.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vortex-layout/src/layouts/zoned/zone_map.rs b/vortex-layout/src/layouts/zoned/zone_map.rs index b2d5719addb..805bc1848bb 100644 --- a/vortex-layout/src/layouts/zoned/zone_map.rs +++ b/vortex-layout/src/layouts/zoned/zone_map.rs @@ -168,9 +168,10 @@ impl ZoneMap { } } -/// This is equivalent to fill_null(false)?.execute::(ctx). -/// However, prune work is very fast, and fill_null + optimize calls -/// dominate the runtime. +/// This is equivalent to `fill_null(false)?.execute::(ctx)`. +/// However, in the case of very frequend and very short operations the cost of +/// filling nulls and then canonicalizing is greater than canonicalizing and +/// then intersecting the bitbuffer. fn null_as_false_mask(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { if !array.dtype().is_nullable() { return array.execute::(ctx);