From 0b1976c8101d3d84187fc44abaf53772e545daeb Mon Sep 17 00:00:00 2001 From: Connor Tsui Date: Tue, 25 Aug 2026 16:14:25 -0400 Subject: [PATCH 1/2] Pack deferred Boolean RowFn output directly Signed-off-by: Connor Tsui --- .../src/scalar_fn/unstable/row/batch/tests.rs | 86 +++++++++++++++++++ .../scalar_fn/unstable/row/execute/owned.rs | 22 ++--- .../unstable/row/types/element/bool.rs | 32 +++++++ .../unstable/row/types/element/output.rs | 31 +++++++ 4 files changed, 154 insertions(+), 17 deletions(-) diff --git a/vortex-array/src/scalar_fn/unstable/row/batch/tests.rs b/vortex-array/src/scalar_fn/unstable/row/batch/tests.rs index 8aed0dd6618..9b2eadbbdef 100644 --- a/vortex-array/src/scalar_fn/unstable/row/batch/tests.rs +++ b/vortex-array/src/scalar_fn/unstable/row/batch/tests.rs @@ -80,6 +80,9 @@ struct PackedPositive; #[derive(Clone)] struct PackedGreaterThan; +#[derive(Clone)] +struct DeferredGreaterThan; + #[derive(Clone)] struct ValidOnlyPositive; @@ -528,6 +531,35 @@ impl RowFn for PackedGreaterThan { } } +impl RowFn for DeferredGreaterThan { + type Options = EmptyOptions; + + const ARG_NAMES: &'static [&'static str] = &["lhs", "rhs"]; + const INFALLIBLE: bool = false; + + fn id(&self) -> ScalarFnId { + static ID: CachedId = CachedId::new("test.deferred_greater_than"); + *ID + } + + fn dispatch( + &self, + _options: &Self::Options, + _args: &[DType], + visitor: V, + ) -> VortexResult { + visitor.visit_deferred::<(i64, i64), bool, bool>( + |(lhs, rhs)| (lhs > rhs, lhs == i64::MIN), + |failed| { + if failed { + vortex_bail!(InvalidArgument: "deferred comparison failed"); + } + + Ok(()) + }, + ) + } +} impl RowFn for ValidOnlyPositive { type Options = EmptyOptions; @@ -760,6 +792,60 @@ fn test_filter_and_scatter_preserves_runtime_sink_params( Ok(()) } +#[test] +fn test_deferred_bool_output_builds_packed_values() -> VortexResult<()> { + let values: Vec<_> = (0_i64..65).collect(); + let lhs = PrimitiveArray::from_iter(values.iter().copied()).into_array(); + let rhs = PrimitiveArray::from_iter(std::iter::repeat_n(32_i64, values.len())).into_array(); + let args = VecExecutionArgs::new(vec![lhs, rhs], values.len()); + let mut ctx = array_session().create_execution_ctx(); + + let actual = execute_rows(&DeferredGreaterThan, &EmptyOptions, &args, &mut ctx)?; + let expected = BoolArray::from_iter(values.iter().map(|value| *value > 32)).into_array(); + + assert_arrays_eq!(&actual, &expected, &mut ctx); + Ok(()) +} + +#[test] +fn test_deferred_bool_output_handles_partial_constants() -> VortexResult<()> { + let values: Vec<_> = (0_i64..65).collect(); + let varying = PrimitiveArray::from_iter(values.iter().copied()).into_array(); + let constant = ConstantArray::new(32_i64, values.len()).into_array(); + let mut ctx = array_session().create_execution_ctx(); + + let lhs_varying = VecExecutionArgs::new(vec![varying.clone(), constant.clone()], values.len()); + let actual = execute_rows(&DeferredGreaterThan, &EmptyOptions, &lhs_varying, &mut ctx)?; + let expected = BoolArray::from_iter(values.iter().map(|value| *value > 32)).into_array(); + assert_arrays_eq!(&actual, &expected, &mut ctx); + + let rhs_varying = VecExecutionArgs::new(vec![constant, varying], values.len()); + let actual = execute_rows(&DeferredGreaterThan, &EmptyOptions, &rhs_varying, &mut ctx)?; + let expected = BoolArray::from_iter(values.iter().map(|value| 32 > *value)).into_array(); + assert_arrays_eq!(&actual, &expected, &mut ctx); + + Ok(()) +} + +#[test] +fn test_deferred_bool_output_reports_valid_row_failure() -> VortexResult<()> { + let lhs = PrimitiveArray::from_iter([1_i64, i64::MIN, -1]).into_array(); + let rhs = ConstantArray::new(0_i64, 3).into_array(); + let args = VecExecutionArgs::new(vec![lhs, rhs], 3); + let mut ctx = array_session().create_execution_ctx(); + + let error = match execute_rows(&DeferredGreaterThan, &EmptyOptions, &args, &mut ctx) { + Err(error) => error.to_string(), + Ok(_) => vortex_bail!("a valid-row deferred failure was not reported"), + }; + + assert!( + error.contains("deferred comparison failed"), + "fallible execution must report its deferred error, got {error}", + ); + Ok(()) +} + #[test] fn test_deferred_owned_execution_handles_constant_lhs() -> VortexResult<()> { let lhs = ConstantArray::new(10_i64, 3).into_array(); diff --git a/vortex-array/src/scalar_fn/unstable/row/execute/owned.rs b/vortex-array/src/scalar_fn/unstable/row/execute/owned.rs index 0224e75eb03..81ded631a16 100644 --- a/vortex-array/src/scalar_fn/unstable/row/execute/owned.rs +++ b/vortex-array/src/scalar_fn/unstable/row/execute/owned.rs @@ -3,13 +3,12 @@ //! Executes row kernels that return one independent owned value per row. //! -//! [`execute_owned`] writes fallible row results into spare vector capacity and reduces compact -//! failure evidence outside the hot loop. [`execute_owned_infallible`] lets the output type map a -//! validated row source directly into its physical representation. +//! [`execute_owned`] and [`execute_owned_infallible`] let the output type map a validated row +//! source directly into its physical representation. [`execute_owned`] also reduces compact +//! failure evidence while collecting the output, before constructing an error. use std::ops::BitOrAssign; -use vortex_compute::lane_kernels::IndexedSourceExt; use vortex_error::VortexResult; use vortex_error::vortex_bail; use vortex_error::vortex_ensure; @@ -165,29 +164,18 @@ where Out: OutputElement, Fail: FailureEvidence, { - // The output vector stays at length zero until every slot is initialized so that an unwind - // abandons partially initialized spare capacity. This no-drop assertion proves that no - // initialized value requires a destructor to run. const { assert_owned_output_needs_no_drop::() }; let columns = Args::decode(args, ctx)?; let prepared = prepare(Args::const_values(&columns)); - let row_count = args.row_count(); - let mut values = Vec::::with_capacity(row_count); - let output = &mut values.spare_capacity_mut()[..row_count]; let Some(source) = decoded_source::(&columns, row_count) else { vortex_bail!("a decoded row input does not address exactly {row_count} rows"); }; - let failure = source.map_checked_into(output, |elements| apply(&prepared, elements)); - - // SAFETY: normal completion initializes `0..row_count` exactly once, and `values` was - // allocated with at least `row_count` capacity. - unsafe { values.set_len(row_count) }; - + let (output, failure) = Out::build_from_deferred(source, |elements| apply(&prepared, elements)); // Defer rich error construction until after the row loop. finish_failure(failure)?; - Ok(Out::build(values)) + Ok(output) } diff --git a/vortex-array/src/scalar_fn/unstable/row/types/element/bool.rs b/vortex-array/src/scalar_fn/unstable/row/types/element/bool.rs index 26028335a37..9dcefa6b66b 100644 --- a/vortex-array/src/scalar_fn/unstable/row/types/element/bool.rs +++ b/vortex-array/src/scalar_fn/unstable/row/types/element/bool.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +use std::mem::size_of; + use vortex_buffer::BitBuffer; use vortex_compute::lane_kernels::IndexedSource; use vortex_error::VortexResult; @@ -12,6 +14,7 @@ use crate::IntoArray; use crate::arrays::BoolArray; use crate::dtype::DType; use crate::dtype::Nullability; +use crate::scalar_fn::unstable::row::FailureEvidence; use crate::scalar_fn::unstable::row::InputElement; use crate::scalar_fn::unstable::row::OutputElement; use crate::validity::Validity; @@ -90,4 +93,33 @@ impl OutputElement for bool { BoolArray::new(values, Validity::NonNullable).into_array() } + + fn build_from_deferred(source: S, apply: F) -> (ArrayRef, Fail) + where + S: IndexedSource, + F: Fn(S::Item) -> (Self, Fail), + Fail: FailureEvidence, + { + const { + assert!( + size_of::() <= size_of::(), + "failure evidence must be no wider than the value, or it bounds the vector width" + ) + }; + + let len = source.len(); + let mut failure = Fail::default(); + let values = BitBuffer::collect_bool(len, |index| { + // SAFETY: `collect_bool` only invokes this closure with `index < len`, and + // `len` is `source.len()`. + let (value, row_failure) = apply(unsafe { source.get_unchecked(index) }); + failure |= row_failure; + value + }); + + ( + BoolArray::new(values, Validity::NonNullable).into_array(), + failure, + ) + } } diff --git a/vortex-array/src/scalar_fn/unstable/row/types/element/output.rs b/vortex-array/src/scalar_fn/unstable/row/types/element/output.rs index 3b92107931b..7d2c86864f7 100644 --- a/vortex-array/src/scalar_fn/unstable/row/types/element/output.rs +++ b/vortex-array/src/scalar_fn/unstable/row/types/element/output.rs @@ -10,6 +10,7 @@ use vortex_compute::lane_kernels::IndexedSourceExt; use crate::ArrayRef; use crate::dtype::DType; +use crate::scalar_fn::unstable::row::FailureEvidence; /// An owned row value that can be built into an all-valid column. /// @@ -60,4 +61,34 @@ pub trait OutputElement: 'static + Sized + Default { Self::build(values) } + + /// Map a contiguous row source into an all-valid column and reduce deferred failure evidence. + /// + /// The default collects one value per row into a [`Vec`] before calling [`build`](Self::build). + /// An output type can override this method to combine its physical collection with the failure + /// reduction. The implementation **must** satisfy the ordering and output requirements of + /// [`build_from`](Self::build_from). It must return the bitwise OR of exactly the failure values + /// returned by `apply` and must not introduce a separate failure path. + /// `Fail` must be no wider than `Self`, so failure reduction does not bound the vector width. + /// + /// The caller validates the failure evidence after this method returns, so it can discard the + /// returned column. + fn build_from_deferred(source: S, apply: F) -> (ArrayRef, Fail) + where + S: IndexedSource, + F: Fn(S::Item) -> (Self, Fail), + Fail: FailureEvidence, + { + let row_count = source.len(); + let mut values = Vec::::with_capacity(row_count); + let output = &mut values.spare_capacity_mut()[..row_count]; + + let failure = source.map_checked_into(output, apply); + + // SAFETY: normal completion of `map_checked_into` initializes every output slot exactly + // once. + unsafe { values.set_len(row_count) }; + + (Self::build(values), failure) + } } From 8468bbdb4ff1495f35dd59547e8bd3ef7c689082 Mon Sep 17 00:00:00 2001 From: Connor Tsui Date: Thu, 27 Aug 2026 10:50:50 -0400 Subject: [PATCH 2/2] Specialize deferred Boolean RowFn output Signed-off-by: Connor Tsui --- vortex-array/benches/row_fn_output.rs | 2 +- .../src/scalar_fn/unstable/row/batch/tests.rs | 2 +- .../src/scalar_fn/unstable/row/execute/mod.rs | 3 + .../scalar_fn/unstable/row/execute/owned.rs | 22 +++++-- .../unstable/row/execute/packed_bool.rs | 64 +++++++++++++++++++ .../unstable/row/types/element/bool.rs | 32 ---------- .../unstable/row/types/element/output.rs | 31 --------- .../scalar_fn/unstable/row/visitor/execute.rs | 28 ++++++++ .../unstable/row/visitor/row_visitor.rs | 35 ++++++++++ 9 files changed, 149 insertions(+), 70 deletions(-) create mode 100644 vortex-array/src/scalar_fn/unstable/row/execute/packed_bool.rs diff --git a/vortex-array/benches/row_fn_output.rs b/vortex-array/benches/row_fn_output.rs index 354544deb25..9d0784d7f58 100644 --- a/vortex-array/benches/row_fn_output.rs +++ b/vortex-array/benches/row_fn_output.rs @@ -132,7 +132,7 @@ impl RowFn for DeferredBool { _args: &[DType], visitor: V, ) -> VortexResult { - visitor.visit_deferred::<(T, T), bool, bool>( + visitor.visit_deferred_bool::<(T, T), bool>( |(lhs, rhs)| (lhs.is_lt(rhs), lhs.is_lt(T::default())), |negative| { vortex_ensure!( diff --git a/vortex-array/src/scalar_fn/unstable/row/batch/tests.rs b/vortex-array/src/scalar_fn/unstable/row/batch/tests.rs index 9b2eadbbdef..6785276610e 100644 --- a/vortex-array/src/scalar_fn/unstable/row/batch/tests.rs +++ b/vortex-array/src/scalar_fn/unstable/row/batch/tests.rs @@ -548,7 +548,7 @@ impl RowFn for DeferredGreaterThan { _args: &[DType], visitor: V, ) -> VortexResult { - visitor.visit_deferred::<(i64, i64), bool, bool>( + visitor.visit_deferred_bool::<(i64, i64), bool>( |(lhs, rhs)| (lhs > rhs, lhs == i64::MIN), |failed| { if failed { diff --git a/vortex-array/src/scalar_fn/unstable/row/execute/mod.rs b/vortex-array/src/scalar_fn/unstable/row/execute/mod.rs index 3ed0b4801f4..61cbc1f478f 100644 --- a/vortex-array/src/scalar_fn/unstable/row/execute/mod.rs +++ b/vortex-array/src/scalar_fn/unstable/row/execute/mod.rs @@ -12,6 +12,9 @@ pub(super) use owned::execute_owned_infallible; pub(super) use owned::execute_owned_infallible_valid_rows; pub(super) use owned::execute_owned_valid_rows; +mod packed_bool; +pub(super) use packed_bool::execute_owned_bool; + mod retry; pub(super) use retry::DenseAttempt; pub(super) use retry::execute_owned_dense_attempt; diff --git a/vortex-array/src/scalar_fn/unstable/row/execute/owned.rs b/vortex-array/src/scalar_fn/unstable/row/execute/owned.rs index 81ded631a16..0224e75eb03 100644 --- a/vortex-array/src/scalar_fn/unstable/row/execute/owned.rs +++ b/vortex-array/src/scalar_fn/unstable/row/execute/owned.rs @@ -3,12 +3,13 @@ //! Executes row kernels that return one independent owned value per row. //! -//! [`execute_owned`] and [`execute_owned_infallible`] let the output type map a validated row -//! source directly into its physical representation. [`execute_owned`] also reduces compact -//! failure evidence while collecting the output, before constructing an error. +//! [`execute_owned`] writes fallible row results into spare vector capacity and reduces compact +//! failure evidence outside the hot loop. [`execute_owned_infallible`] lets the output type map a +//! validated row source directly into its physical representation. use std::ops::BitOrAssign; +use vortex_compute::lane_kernels::IndexedSourceExt; use vortex_error::VortexResult; use vortex_error::vortex_bail; use vortex_error::vortex_ensure; @@ -164,18 +165,29 @@ where Out: OutputElement, Fail: FailureEvidence, { + // The output vector stays at length zero until every slot is initialized so that an unwind + // abandons partially initialized spare capacity. This no-drop assertion proves that no + // initialized value requires a destructor to run. const { assert_owned_output_needs_no_drop::() }; let columns = Args::decode(args, ctx)?; let prepared = prepare(Args::const_values(&columns)); + let row_count = args.row_count(); + let mut values = Vec::::with_capacity(row_count); + let output = &mut values.spare_capacity_mut()[..row_count]; let Some(source) = decoded_source::(&columns, row_count) else { vortex_bail!("a decoded row input does not address exactly {row_count} rows"); }; - let (output, failure) = Out::build_from_deferred(source, |elements| apply(&prepared, elements)); + let failure = source.map_checked_into(output, |elements| apply(&prepared, elements)); + + // SAFETY: normal completion initializes `0..row_count` exactly once, and `values` was + // allocated with at least `row_count` capacity. + unsafe { values.set_len(row_count) }; + // Defer rich error construction until after the row loop. finish_failure(failure)?; - Ok(output) + Ok(Out::build(values)) } diff --git a/vortex-array/src/scalar_fn/unstable/row/execute/packed_bool.rs b/vortex-array/src/scalar_fn/unstable/row/execute/packed_bool.rs new file mode 100644 index 00000000000..3f9c5aa3fbb --- /dev/null +++ b/vortex-array/src/scalar_fn/unstable/row/execute/packed_bool.rs @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Direct packed Boolean collection for deferred row computations. + +use std::mem::size_of; + +use vortex_buffer::BitBuffer; +use vortex_compute::lane_kernels::IndexedSource; +use vortex_error::VortexResult; +use vortex_error::vortex_bail; + +use crate::ArrayRef; +use crate::ExecutionCtx; +use crate::IntoArray; +use crate::arrays::BoolArray; +use crate::scalar_fn::ExecutionArgs; +use crate::scalar_fn::unstable::row::FailureEvidence; +use crate::scalar_fn::unstable::row::IndexedElementTuple; +use crate::scalar_fn::unstable::row::types::decoded_source; +use crate::validity::Validity; + +/// Decode every input column, then pack Boolean outputs while combining failure evidence. +pub(crate) fn execute_owned_bool( + args: &dyn ExecutionArgs, + ctx: &mut ExecutionCtx, + prepare: impl FnOnce(Args::ConstElems<'_>) -> Prepared, + apply: impl Fn(&Prepared, Args::Elems<'_>) -> (bool, Fail), + finish_failure: impl FnOnce(Fail) -> VortexResult<()>, +) -> VortexResult +where + Args: IndexedElementTuple, + Fail: FailureEvidence, +{ + const { + assert!( + size_of::() <= size_of::(), + "failure evidence must be no wider than the value, or it bounds the vector width" + ) + }; + + let columns = Args::decode(args, ctx)?; + let prepared = prepare(Args::const_values(&columns)); + let row_count = args.row_count(); + + let Some(source) = decoded_source::(&columns, row_count) else { + vortex_bail!("a decoded row input does not address exactly {row_count} rows"); + }; + + let mut failure = Fail::default(); + let values = BitBuffer::collect_bool(row_count, |index| { + // SAFETY: `collect_bool` only invokes this closure with `index < row_count`, and the + // decoded source was constructed with exactly `row_count` rows. + let elements = unsafe { source.get_unchecked(index) }; + let (value, row_failure) = apply(&prepared, elements); + failure |= row_failure; + + value + }); + + finish_failure(failure)?; + + Ok(BoolArray::new(values, Validity::NonNullable).into_array()) +} diff --git a/vortex-array/src/scalar_fn/unstable/row/types/element/bool.rs b/vortex-array/src/scalar_fn/unstable/row/types/element/bool.rs index 9dcefa6b66b..26028335a37 100644 --- a/vortex-array/src/scalar_fn/unstable/row/types/element/bool.rs +++ b/vortex-array/src/scalar_fn/unstable/row/types/element/bool.rs @@ -1,8 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -use std::mem::size_of; - use vortex_buffer::BitBuffer; use vortex_compute::lane_kernels::IndexedSource; use vortex_error::VortexResult; @@ -14,7 +12,6 @@ use crate::IntoArray; use crate::arrays::BoolArray; use crate::dtype::DType; use crate::dtype::Nullability; -use crate::scalar_fn::unstable::row::FailureEvidence; use crate::scalar_fn::unstable::row::InputElement; use crate::scalar_fn::unstable::row::OutputElement; use crate::validity::Validity; @@ -93,33 +90,4 @@ impl OutputElement for bool { BoolArray::new(values, Validity::NonNullable).into_array() } - - fn build_from_deferred(source: S, apply: F) -> (ArrayRef, Fail) - where - S: IndexedSource, - F: Fn(S::Item) -> (Self, Fail), - Fail: FailureEvidence, - { - const { - assert!( - size_of::() <= size_of::(), - "failure evidence must be no wider than the value, or it bounds the vector width" - ) - }; - - let len = source.len(); - let mut failure = Fail::default(); - let values = BitBuffer::collect_bool(len, |index| { - // SAFETY: `collect_bool` only invokes this closure with `index < len`, and - // `len` is `source.len()`. - let (value, row_failure) = apply(unsafe { source.get_unchecked(index) }); - failure |= row_failure; - value - }); - - ( - BoolArray::new(values, Validity::NonNullable).into_array(), - failure, - ) - } } diff --git a/vortex-array/src/scalar_fn/unstable/row/types/element/output.rs b/vortex-array/src/scalar_fn/unstable/row/types/element/output.rs index 7d2c86864f7..3b92107931b 100644 --- a/vortex-array/src/scalar_fn/unstable/row/types/element/output.rs +++ b/vortex-array/src/scalar_fn/unstable/row/types/element/output.rs @@ -10,7 +10,6 @@ use vortex_compute::lane_kernels::IndexedSourceExt; use crate::ArrayRef; use crate::dtype::DType; -use crate::scalar_fn::unstable::row::FailureEvidence; /// An owned row value that can be built into an all-valid column. /// @@ -61,34 +60,4 @@ pub trait OutputElement: 'static + Sized + Default { Self::build(values) } - - /// Map a contiguous row source into an all-valid column and reduce deferred failure evidence. - /// - /// The default collects one value per row into a [`Vec`] before calling [`build`](Self::build). - /// An output type can override this method to combine its physical collection with the failure - /// reduction. The implementation **must** satisfy the ordering and output requirements of - /// [`build_from`](Self::build_from). It must return the bitwise OR of exactly the failure values - /// returned by `apply` and must not introduce a separate failure path. - /// `Fail` must be no wider than `Self`, so failure reduction does not bound the vector width. - /// - /// The caller validates the failure evidence after this method returns, so it can discard the - /// returned column. - fn build_from_deferred(source: S, apply: F) -> (ArrayRef, Fail) - where - S: IndexedSource, - F: Fn(S::Item) -> (Self, Fail), - Fail: FailureEvidence, - { - let row_count = source.len(); - let mut values = Vec::::with_capacity(row_count); - let output = &mut values.spare_capacity_mut()[..row_count]; - - let failure = source.map_checked_into(output, apply); - - // SAFETY: normal completion of `map_checked_into` initializes every output slot exactly - // once. - unsafe { values.set_len(row_count) }; - - (Self::build(values), failure) - } } diff --git a/vortex-array/src/scalar_fn/unstable/row/visitor/execute.rs b/vortex-array/src/scalar_fn/unstable/row/visitor/execute.rs index 0501d6fc6a0..b3a747e116c 100644 --- a/vortex-array/src/scalar_fn/unstable/row/visitor/execute.rs +++ b/vortex-array/src/scalar_fn/unstable/row/visitor/execute.rs @@ -33,6 +33,7 @@ use crate::scalar_fn::unstable::row::OutputSink; use crate::scalar_fn::unstable::row::RowFn; use crate::scalar_fn::unstable::row::SinkResult; use crate::scalar_fn::unstable::row::execute::execute_owned; +use crate::scalar_fn::unstable::row::execute::execute_owned_bool; use crate::scalar_fn::unstable::row::execute::execute_owned_infallible; use crate::scalar_fn::unstable::row::execute::execute_owned_infallible_valid_rows; use crate::scalar_fn::unstable::row::execute::execute_owned_valid_rows; @@ -159,6 +160,33 @@ impl RowVisitor for ExecuteRows<'_, '_, F> { finish_failure, ) } + + fn visit_prepared_deferred_bool( + self, + prepare: impl FnOnce(Args::ConstElems<'_>) -> Prepared, + apply: impl Fn(&Prepared, Args::Elems<'_>) -> (bool, Fail), + finish_failure: impl FnOnce(Fail) -> VortexResult<()>, + ) -> VortexResult + where + Args: IndexedElementTuple, + Fail: FailureEvidence, + { + const { assert_deferred_visit_contract::() }; + let visited = BatchPlan::new( + validate_owned_visit::(self.dtypes)?, + self.output_dtype, + RowPolicy::for_deferred_output::(), + )?; + self.plan.ensure_reproduced_by(&visited)?; + + execute_owned_bool::( + self.args, + self.ctx, + prepare, + apply, + finish_failure, + ) + } } /// The runtime visit that executes valid rows over the original input columns. diff --git a/vortex-array/src/scalar_fn/unstable/row/visitor/row_visitor.rs b/vortex-array/src/scalar_fn/unstable/row/visitor/row_visitor.rs index 5d8c6a9b57a..77d999c776e 100644 --- a/vortex-array/src/scalar_fn/unstable/row/visitor/row_visitor.rs +++ b/vortex-array/src/scalar_fn/unstable/row/visitor/row_visitor.rs @@ -297,6 +297,27 @@ pub trait RowVisitor: private::Sealed + Sized { ) } + /// Visit a deferred row computation whose Boolean output is packed during evaluation. + /// + /// This has the same requirements and failure handling as + /// [`visit_deferred`](Self::visit_deferred). It selects direct packed collection instead of the + /// generic owned-output path. + fn visit_deferred_bool( + self, + apply: impl Fn(Args::Elems<'_>) -> (bool, Fail), + finish_failure: impl FnOnce(Fail) -> VortexResult<()>, + ) -> VortexResult + where + Args: IndexedElementTuple, + Fail: FailureEvidence, + { + self.visit_prepared_deferred_bool::( + |_| (), + move |&(), args| apply(args), + finish_failure, + ) + } + /// The prepared form of [`visit_deferred`](Self::visit_deferred), with the same prerequisites. /// /// # Examples @@ -342,6 +363,20 @@ pub trait RowVisitor: private::Sealed + Sized { Args: IndexedElementTuple, Out: OutputElement, Fail: FailureEvidence; + + /// The prepared form of [`visit_deferred_bool`](Self::visit_deferred_bool). + fn visit_prepared_deferred_bool( + self, + prepare: impl FnOnce(Args::ConstElems<'_>) -> Prepared, + apply: impl Fn(&Prepared, Args::Elems<'_>) -> (bool, Fail), + finish_failure: impl FnOnce(Fail) -> VortexResult<()>, + ) -> VortexResult + where + Args: IndexedElementTuple, + Fail: FailureEvidence, + { + self.visit_prepared_deferred::(prepare, apply, finish_failure) + } } pub(super) mod private {