Skip to main content

vortex_array/scalar_fn/fns/fill_null/
kernel.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use vortex_error::VortexExpect;
5use vortex_error::VortexResult;
6use vortex_error::vortex_ensure;
7
8use crate::ArrayRef;
9use crate::ExecutionCtx;
10use crate::IntoArray;
11use crate::array::ArrayView;
12use crate::array::VTable;
13use crate::arrays::Constant;
14use crate::arrays::ConstantArray;
15use crate::arrays::ScalarFnVTable;
16use crate::arrays::scalar_fn::ExactScalarFn;
17use crate::arrays::scalar_fn::ScalarFnArrayExt;
18use crate::arrays::scalar_fn::ScalarFnArrayView;
19use crate::builtins::ArrayBuiltins;
20use crate::kernel::ExecuteParentKernel;
21use crate::optimizer::rules::ArrayParentReduceRule;
22use crate::scalar::Scalar;
23use crate::scalar_fn::fns::fill_null::FillNull as FillNullExpr;
24
25/// Fill nulls in an array with a scalar value without reading buffers.
26///
27/// This trait is for fill_null implementations that can operate purely on array metadata
28/// and structure without needing to read or execute on the underlying buffers.
29/// Implementations should return `None` if the operation requires buffer access.
30///
31/// # Preconditions
32///
33/// The fill value is guaranteed to be non-null. The array is guaranteed to have mixed
34/// validity (neither all-valid nor all-invalid).
35pub trait FillNullReduce: VTable {
36    fn fill_null(array: ArrayView<'_, Self>, fill_value: &Scalar)
37    -> VortexResult<Option<ArrayRef>>;
38}
39
40/// Fill nulls in an array with a scalar value, potentially reading buffers.
41///
42/// Unlike [`FillNullReduce`], this trait is for fill_null implementations that may need
43/// to read and execute on the underlying buffers to produce the result.
44///
45/// # Preconditions
46///
47/// The fill value is guaranteed to be non-null. The array is guaranteed to have mixed
48/// validity (neither all-valid nor all-invalid).
49pub trait FillNullKernel: VTable {
50    fn fill_null(
51        array: ArrayView<'_, Self>,
52        fill_value: &Scalar,
53        ctx: &mut ExecutionCtx,
54    ) -> VortexResult<Option<ArrayRef>>;
55}
56
57/// Common preconditions for fill_null operations that apply to all arrays.
58///
59/// Returns `Some(result)` if the precondition short-circuits the fill_null operation,
60/// or `None` if fill_null should proceed with the encoding-specific implementation.
61pub(super) fn precondition(
62    array: &ArrayRef,
63    fill_value: &Scalar,
64) -> VortexResult<Option<ArrayRef>> {
65    vortex_ensure!(
66        !fill_value.is_null(),
67        "fill_null requires a non-null fill value"
68    );
69
70    // If the array has no nulls, fill_null is a no-op (just cast for nullability).
71    if !array.dtype().is_nullable() || array.all_valid()? {
72        return array.clone().cast(fill_value.dtype().clone()).map(Some);
73    }
74
75    // If all values are null, replace the entire array with the fill value.
76    if array.all_invalid()? {
77        return Ok(Some(
78            ConstantArray::new(fill_value.clone(), array.len()).into_array(),
79        ));
80    }
81
82    Ok(None)
83}
84
85/// Fill null on a [`ConstantArray`] by replacing null scalars with the fill value,
86/// or casting non-null scalars to the fill value's dtype.
87pub(crate) fn fill_null_constant(
88    array: ArrayView<Constant>,
89    fill_value: &Scalar,
90) -> VortexResult<ArrayRef> {
91    let scalar = if array.scalar().is_null() {
92        fill_value.clone()
93    } else {
94        array.scalar().cast(fill_value.dtype())?
95    };
96    Ok(ConstantArray::new(scalar, array.len()).into_array())
97}
98
99/// Adaptor that wraps a [`FillNullReduce`] impl as an [`ArrayParentReduceRule`].
100#[derive(Default, Debug)]
101pub struct FillNullReduceAdaptor<V>(pub V);
102
103impl<V> ArrayParentReduceRule<V> for FillNullReduceAdaptor<V>
104where
105    V: FillNullReduce,
106{
107    type Parent = ExactScalarFn<FillNullExpr>;
108
109    fn reduce_parent(
110        &self,
111        array: ArrayView<'_, V>,
112        parent: ScalarFnArrayView<'_, FillNullExpr>,
113        child_idx: usize,
114    ) -> VortexResult<Option<ArrayRef>> {
115        // Only process the input child (index 0), not the fill_value child (index 1).
116        if child_idx != 0 {
117            return Ok(None);
118        }
119        let scalar_fn_array = parent
120            .as_opt::<ScalarFnVTable>()
121            .vortex_expect("ExactScalarFn matcher confirmed ScalarFnArray");
122        let fill_value = scalar_fn_array
123            .get_child(1)
124            .as_constant()
125            .vortex_expect("fill_null fill_value must be constant");
126        let arr = array.array().clone();
127        if let Some(result) = precondition(&arr, &fill_value)? {
128            return Ok(Some(result));
129        }
130        <V as FillNullReduce>::fill_null(array, &fill_value)
131    }
132}
133
134/// Adaptor that wraps a [`FillNullKernel`] impl as an [`ExecuteParentKernel`].
135#[derive(Default, Debug)]
136pub struct FillNullExecuteAdaptor<V>(pub V);
137
138impl<V> ExecuteParentKernel<V> for FillNullExecuteAdaptor<V>
139where
140    V: FillNullKernel,
141{
142    type Parent = ExactScalarFn<FillNullExpr>;
143
144    fn execute_parent(
145        &self,
146        array: ArrayView<'_, V>,
147        parent: ScalarFnArrayView<'_, FillNullExpr>,
148        child_idx: usize,
149        ctx: &mut ExecutionCtx,
150    ) -> VortexResult<Option<ArrayRef>> {
151        // Only process the input child (index 0), not the fill_value child (index 1).
152        if child_idx != 0 {
153            return Ok(None);
154        }
155        let scalar_fn_array = parent
156            .as_opt::<ScalarFnVTable>()
157            .vortex_expect("ExactScalarFn matcher confirmed ScalarFnArray");
158        let fill_value = scalar_fn_array
159            .get_child(1)
160            .as_constant()
161            .vortex_expect("fill_null fill_value must be constant");
162        let arr = array.array().clone();
163        if let Some(result) = precondition(&arr, &fill_value)? {
164            return Ok(Some(result));
165        }
166        <V as FillNullKernel>::fill_null(array, &fill_value, ctx)
167    }
168}