|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// SPDX-FileCopyrightText: Copyright the Vortex contributors |
| 3 | + |
| 4 | +use smallvec::SmallVec; |
| 5 | +use vortex_error::VortexResult; |
| 6 | +use vortex_error::vortex_bail; |
| 7 | +use vortex_error::vortex_ensure_eq; |
| 8 | +use vortex_mask::AllOr; |
| 9 | +use vortex_mask::Mask; |
| 10 | + |
| 11 | +use super::super::Batch; |
| 12 | +use super::super::args::BorrowedExecutionArgs; |
| 13 | +use crate::ArrayRef; |
| 14 | +use crate::ExecutionCtx; |
| 15 | +use crate::IntoArray; |
| 16 | +use crate::arrays::BoolArray; |
| 17 | +use crate::arrays::MaskedArray; |
| 18 | +use crate::arrays::PrimitiveArray; |
| 19 | +use crate::builtins::ArrayBuiltins; |
| 20 | +use crate::dtype::Nullability; |
| 21 | +use crate::scalar_fn::unstable::row::execute::RowExecution; |
| 22 | +use crate::validity::Validity; |
| 23 | + |
| 24 | +impl Batch { |
| 25 | + /// Filter to valid rows, run the kernel, then scatter into a null-padded output. |
| 26 | + pub(super) fn filter_and_scatter( |
| 27 | + &self, |
| 28 | + kernel: impl Fn(BorrowedExecutionArgs<'_>, &mut ExecutionCtx) -> VortexResult<RowExecution>, |
| 29 | + valid: &Mask, |
| 30 | + ctx: &mut ExecutionCtx, |
| 31 | + ) -> VortexResult<ArrayRef> { |
| 32 | + let filtered: SmallVec<[ArrayRef; 4]> = self |
| 33 | + .inputs |
| 34 | + .iter() |
| 35 | + .map(|input| input.filter(valid.clone())) |
| 36 | + .collect::<VortexResult<_>>()?; |
| 37 | + |
| 38 | + let values = VortexResult::from(kernel( |
| 39 | + self.execution_args(&filtered, valid.true_count()), |
| 40 | + ctx, |
| 41 | + )?)?; |
| 42 | + let values = self.validate_kernel_output(values, valid.true_count(), ctx)?; |
| 43 | + |
| 44 | + self.finalize_output(self.scatter_valid(values, valid)?, valid.len()) |
| 45 | + } |
| 46 | + |
| 47 | + /// Scatter `values` (one per set bit of `valid`, in order) back to the positions of the set |
| 48 | + /// bits, producing an array of length `valid.len()` that is null at every unset position. |
| 49 | + fn scatter_valid(&self, values: ArrayRef, valid: &Mask) -> VortexResult<ArrayRef> { |
| 50 | + vortex_ensure_eq!( |
| 51 | + values.len(), |
| 52 | + valid.true_count(), |
| 53 | + "the {} kernel output must contain {} filtered rows, got {}", |
| 54 | + self.id, |
| 55 | + valid.true_count(), |
| 56 | + values.len(), |
| 57 | + ); |
| 58 | + |
| 59 | + let AllOr::Some(slices) = valid.slices() else { |
| 60 | + // The caller handles the all-true and all-false masks. |
| 61 | + vortex_bail!( |
| 62 | + "scatter_valid requires valid and invalid rows, got an all-valid or all-invalid mask" |
| 63 | + ); |
| 64 | + }; |
| 65 | + |
| 66 | + // Gather indices: row i of the output reads values[rank(i)]. Rows behind nulls read index |
| 67 | + // 0, and any in-bounds index would do since they are masked out below (values is non-empty |
| 68 | + // here). |
| 69 | + let mut indices = vec![0u64; valid.len()]; |
| 70 | + let mut rank = 0u64; |
| 71 | + for &(start, end) in slices { |
| 72 | + for index in &mut indices[start..end] { |
| 73 | + *index = rank; |
| 74 | + rank += 1; |
| 75 | + } |
| 76 | + } |
| 77 | + let indices = PrimitiveArray::new(indices, Validity::NonNullable).into_array(); |
| 78 | + |
| 79 | + let scattered = values.take(indices)?; |
| 80 | + |
| 81 | + // A nullable gathered array cannot be wrapped because a `Masked` child must be all valid. |
| 82 | + // The general masking pass unions its nulls with the batch validity instead. |
| 83 | + if scattered.dtype().is_nullable() { |
| 84 | + let mask = BoolArray::new(valid.to_bit_buffer(), Validity::NonNullable).into_array(); |
| 85 | + return scattered.mask(mask); |
| 86 | + } |
| 87 | + |
| 88 | + // The gathered values are all valid, so attaching validity is sufficient. |
| 89 | + Ok(MaskedArray::try_new( |
| 90 | + scattered, |
| 91 | + Validity::from_mask(valid.clone(), Nullability::Nullable), |
| 92 | + )? |
| 93 | + .into_array()) |
| 94 | + } |
| 95 | +} |
0 commit comments