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::ScalarFn;
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;
24use crate::validity::Validity;
25
26/// Fill nulls in an array with a scalar value without reading buffers.
27///
28/// This trait is for fill_null implementations that can operate purely on array metadata
29/// and structure without needing to read or execute on the underlying buffers.
30/// Implementations should return `None` if the operation requires buffer access.
31///
32/// # Preconditions
33///
34/// The fill value is guaranteed to be non-null. The array is guaranteed to have mixed
35/// validity (neither all-valid nor all-invalid).
36pub trait FillNullReduce: VTable {
37    fn fill_null(array: ArrayView<'_, Self>, fill_value: &Scalar)
38    -> VortexResult<Option<ArrayRef>>;
39}
40
41/// Fill nulls in an array with a scalar value, potentially reading buffers.
42///
43/// Unlike [`FillNullReduce`], this trait is for fill_null implementations that may need
44/// to read and execute on the underlying buffers to produce the result.
45///
46/// # Preconditions
47///
48/// The fill value is guaranteed to be non-null. The array is guaranteed to have mixed
49/// validity (neither all-valid nor all-invalid).
50pub trait FillNullKernel: VTable {
51    fn fill_null(
52        array: ArrayView<'_, Self>,
53        fill_value: &Scalar,
54        ctx: &mut ExecutionCtx,
55    ) -> VortexResult<Option<ArrayRef>>;
56}
57
58/// Short-circuits fill_null for the inputs that need no encoding-specific work.
59///
60/// Returns `Some(result)` when the answer is already known, or `None` when fill_null must proceed
61/// with the encoding-specific implementation. Kernels can therefore rely on a non-null fill value.
62///
63/// The result can be a lazy [`ScalarFn`] array, so a caller that needs a computed array
64/// **must** execute it.
65///
66/// [`ScalarFn`]: crate::arrays::ScalarFn
67pub(super) fn short_circuit(
68    array: &ArrayRef,
69    fill_value: &Scalar,
70) -> VortexResult<Option<ArrayRef>> {
71    vortex_ensure!(
72        !fill_value.is_null(),
73        "fill_null requires a non-null fill value"
74    );
75
76    // If the array has no nulls, fill_null is a no-op (just cast for nullability).
77    if !array.dtype().is_nullable()
78        || matches!(
79            array.validity()?,
80            Validity::NonNullable | Validity::AllValid
81        )
82    {
83        return array.clone().cast(fill_value.dtype().clone()).map(Some);
84    }
85
86    // If all values are null, replace the entire array with the fill value.
87    if array.validity()?.definitely_all_null() {
88        return Ok(Some(
89            ConstantArray::new(fill_value.clone(), array.len()).into_array(),
90        ));
91    }
92
93    Ok(None)
94}
95
96/// Fill null on a [`ConstantArray`] by replacing null scalars with the fill value,
97/// or casting non-null scalars to the fill value's dtype.
98pub(crate) fn fill_null_constant(
99    array: ArrayView<Constant>,
100    fill_value: &Scalar,
101) -> VortexResult<ArrayRef> {
102    let scalar = if array.scalar().is_null() {
103        fill_value.clone()
104    } else {
105        array.scalar().cast(fill_value.dtype())?
106    };
107    Ok(ConstantArray::new(scalar, array.len()).into_array())
108}
109
110/// Adaptor that wraps a [`FillNullReduce`] impl as an [`ArrayParentReduceRule`].
111#[derive(Default, Debug)]
112pub struct FillNullReduceAdaptor<V>(pub V);
113
114impl<V> ArrayParentReduceRule<V> for FillNullReduceAdaptor<V>
115where
116    V: FillNullReduce,
117{
118    type Parent = ExactScalarFn<FillNullExpr>;
119
120    fn reduce_parent(
121        &self,
122        array: ArrayView<'_, V>,
123        parent: ScalarFnArrayView<'_, FillNullExpr>,
124        child_idx: usize,
125    ) -> VortexResult<Option<ArrayRef>> {
126        // Only process the input child (index 0), not the fill_value child (index 1).
127        if child_idx != 0 {
128            return Ok(None);
129        }
130        let scalar_fn_array = parent
131            .as_opt::<ScalarFn>()
132            .vortex_expect("ExactScalarFn matcher confirmed ScalarFnArray");
133        let fill_value = scalar_fn_array
134            .get_child(1)
135            .as_constant()
136            .vortex_expect("fill_null fill_value must be constant");
137        let arr = array.array().clone();
138        if let Some(result) = short_circuit(&arr, &fill_value)? {
139            return Ok(Some(result));
140        }
141        <V as FillNullReduce>::fill_null(array, &fill_value)
142    }
143}
144
145/// Adaptor that wraps a [`FillNullKernel`] impl as an [`ExecuteParentKernel`].
146#[derive(Default, Debug)]
147pub struct FillNullExecuteAdaptor<V>(pub V);
148
149impl<V> ExecuteParentKernel<V> for FillNullExecuteAdaptor<V>
150where
151    V: FillNullKernel,
152{
153    type Parent = ExactScalarFn<FillNullExpr>;
154
155    fn execute_parent(
156        &self,
157        array: ArrayView<'_, V>,
158        parent: ScalarFnArrayView<'_, FillNullExpr>,
159        child_idx: usize,
160        ctx: &mut ExecutionCtx,
161    ) -> VortexResult<Option<ArrayRef>> {
162        // Only process the input child (index 0), not the fill_value child (index 1).
163        if child_idx != 0 {
164            return Ok(None);
165        }
166        let scalar_fn_array = parent
167            .as_opt::<ScalarFn>()
168            .vortex_expect("ExactScalarFn matcher confirmed ScalarFnArray");
169        let fill_value = scalar_fn_array
170            .get_child(1)
171            .as_constant()
172            .vortex_expect("fill_null fill_value must be constant");
173        let arr = array.array().clone();
174        if let Some(result) = short_circuit(&arr, &fill_value)? {
175            return Ok(Some(result));
176        }
177        <V as FillNullKernel>::fill_null(array, &fill_value, ctx)
178    }
179}