vortex_array/compute/
fill_null.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::sync::LazyLock;
5
6use arcref::ArcRef;
7use vortex_dtype::DType;
8use vortex_error::{VortexError, VortexResult, vortex_bail, vortex_err};
9use vortex_scalar::Scalar;
10
11use crate::compute::{ComputeFn, ComputeFnVTable, InvocationArgs, Kernel, Output, cast};
12use crate::vtable::VTable;
13use crate::{Array, ArrayRef, IntoArray};
14
15static FILL_NULL_FN: LazyLock<ComputeFn> = LazyLock::new(|| {
16    let compute = ComputeFn::new("fill_null".into(), ArcRef::new_ref(&FillNull));
17    for kernel in inventory::iter::<FillNullKernelRef> {
18        compute.register_kernel(kernel.0.clone());
19    }
20    compute
21});
22
23pub(crate) fn warm_up_vtable() -> usize {
24    FILL_NULL_FN.kernels().len()
25}
26
27/// Replace nulls in the array with another value.
28///
29/// # Examples
30///
31/// ```
32/// use vortex_array::arrays::{PrimitiveArray};
33/// use vortex_array::compute::{fill_null};
34/// use vortex_scalar::Scalar;
35///
36/// let array =
37///     PrimitiveArray::from_option_iter([Some(0i32), None, Some(1i32), None, Some(2i32)]);
38/// let array = fill_null(array.as_ref(), &Scalar::from(42i32)).unwrap();
39/// assert_eq!(array.display_values().to_string(), "[0i32, 42i32, 1i32, 42i32, 2i32]");
40/// ```
41pub fn fill_null(array: &dyn Array, fill_value: &Scalar) -> VortexResult<ArrayRef> {
42    FILL_NULL_FN
43        .invoke(&InvocationArgs {
44            inputs: &[array.into(), fill_value.into()],
45            options: &(),
46        })?
47        .unwrap_array()
48}
49
50pub trait FillNullKernel: VTable {
51    fn fill_null(&self, array: &Self::Array, fill_value: &Scalar) -> VortexResult<ArrayRef>;
52}
53
54pub struct FillNullKernelRef(ArcRef<dyn Kernel>);
55inventory::collect!(FillNullKernelRef);
56
57#[derive(Debug)]
58pub struct FillNullKernelAdapter<V: VTable>(pub V);
59
60impl<V: VTable + FillNullKernel> FillNullKernelAdapter<V> {
61    pub const fn lift(&'static self) -> FillNullKernelRef {
62        FillNullKernelRef(ArcRef::new_ref(self))
63    }
64}
65
66impl<V: VTable + FillNullKernel> Kernel for FillNullKernelAdapter<V> {
67    fn invoke(&self, args: &InvocationArgs) -> VortexResult<Option<Output>> {
68        let inputs = FillNullArgs::try_from(args)?;
69        let Some(array) = inputs.array.as_opt::<V>() else {
70            return Ok(None);
71        };
72        Ok(Some(
73            V::fill_null(&self.0, array, inputs.fill_value)?.into(),
74        ))
75    }
76}
77
78struct FillNull;
79
80impl ComputeFnVTable for FillNull {
81    fn invoke(
82        &self,
83        args: &InvocationArgs,
84        kernels: &[ArcRef<dyn Kernel>],
85    ) -> VortexResult<Output> {
86        let FillNullArgs { array, fill_value } = FillNullArgs::try_from(args)?;
87
88        if !array.dtype().is_nullable() || array.all_valid() {
89            return Ok(cast(array, fill_value.dtype())?.into());
90        }
91
92        if fill_value.is_null() {
93            vortex_bail!("Cannot fill_null with a null value")
94        }
95
96        for kernel in kernels {
97            if let Some(output) = kernel.invoke(args)? {
98                return Ok(output);
99            }
100        }
101        if let Some(output) = array.invoke(&FILL_NULL_FN, args)? {
102            return Ok(output);
103        }
104
105        log::debug!("FillNullFn not implemented for {}", array.encoding_id());
106        if !array.is_canonical() {
107            let canonical_arr = array.to_canonical().into_array();
108            return Ok(fill_null(canonical_arr.as_ref(), fill_value)?.into());
109        }
110
111        vortex_bail!("fill null not implemented for DType {}", array.dtype())
112    }
113
114    fn return_dtype(&self, args: &InvocationArgs) -> VortexResult<DType> {
115        let FillNullArgs { array, fill_value } = FillNullArgs::try_from(args)?;
116        if !array.dtype().eq_ignore_nullability(fill_value.dtype()) {
117            vortex_bail!("FillNull value must match array type (ignoring nullability)");
118        }
119        Ok(fill_value.dtype().clone())
120    }
121
122    fn return_len(&self, args: &InvocationArgs) -> VortexResult<usize> {
123        let FillNullArgs { array, .. } = FillNullArgs::try_from(args)?;
124        Ok(array.len())
125    }
126
127    fn is_elementwise(&self) -> bool {
128        true
129    }
130}
131
132struct FillNullArgs<'a> {
133    array: &'a dyn Array,
134    fill_value: &'a Scalar,
135}
136
137impl<'a> TryFrom<&InvocationArgs<'a>> for FillNullArgs<'a> {
138    type Error = VortexError;
139
140    fn try_from(value: &InvocationArgs<'a>) -> Result<Self, Self::Error> {
141        if value.inputs.len() != 2 {
142            vortex_bail!("FillNull requires 2 arguments");
143        }
144
145        let array = value.inputs[0]
146            .array()
147            .ok_or_else(|| vortex_err!("FillNull requires an array"))?;
148        let fill_value = value.inputs[1]
149            .scalar()
150            .ok_or_else(|| vortex_err!("FillNull requires a scalar"))?;
151
152        Ok(FillNullArgs { array, fill_value })
153    }
154}