Skip to main content

vortex_array/arrays/scalar_fn/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::fmt::Formatter;
6
7use smallvec::SmallVec;
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10use vortex_error::vortex_bail;
11use vortex_error::vortex_ensure;
12
13use crate::ArrayRef;
14use crate::ArraySlots;
15use crate::array::Array;
16use crate::array::ArrayParts;
17use crate::array::TypedArrayRef;
18use crate::arrays::ScalarFn;
19use crate::dtype::DType;
20use crate::scalar_fn::ScalarFnRef;
21use crate::scalar_fn::fns::literal::Literal;
22
23// ScalarFnArray has a variable number of slots (one per child)
24
25#[derive(Clone, Debug)]
26pub struct ScalarFnData {
27    pub(super) scalar_fn: ScalarFnRef,
28}
29
30impl Display for ScalarFnData {
31    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32        write!(f, "scalar_fn: {}", self.scalar_fn)
33    }
34}
35
36impl ScalarFnData {
37    /// Get the scalar function bound to this array.
38    #[allow(clippy::inline_always)]
39    #[inline(always)]
40    pub fn scalar_fn(&self) -> &ScalarFnRef {
41        &self.scalar_fn
42    }
43}
44
45pub trait ScalarFnArrayExt: TypedArrayRef<ScalarFn> {
46    fn scalar_fn(&self) -> &ScalarFnRef {
47        &self.scalar_fn
48    }
49
50    fn child_at(&self, idx: usize) -> &ArrayRef {
51        self.slots()[idx]
52            .as_ref()
53            .vortex_expect("ScalarFnArray child slot")
54    }
55
56    fn child_count(&self) -> usize {
57        self.slots().len()
58    }
59
60    fn nchildren(&self) -> usize {
61        self.child_count()
62    }
63
64    fn get_child(&self, idx: usize) -> &ArrayRef {
65        self.child_at(idx)
66    }
67
68    fn iter_children(&self) -> impl Iterator<Item = &ArrayRef> + '_ {
69        (0..self.child_count()).map(|idx| self.child_at(idx))
70    }
71
72    fn children(&self) -> Vec<ArrayRef> {
73        self.iter_children().cloned().collect()
74    }
75}
76impl<T: TypedArrayRef<ScalarFn>> ScalarFnArrayExt for T {}
77
78impl Array<ScalarFn> {
79    /// Create a new ScalarFnArray from a scalar function and its children.
80    pub fn try_new(
81        scalar_fn: ScalarFnRef,
82        children: impl IntoIterator<Item = ArrayRef>,
83    ) -> VortexResult<Self> {
84        let slots: ArraySlots = children.into_iter().map(Some).collect();
85        let len = Self::infer_len(&slots)?;
86        Self::try_new_from_slots(scalar_fn, slots, len)
87    }
88
89    /// Create a new ScalarFnArray from a scalar function, children, and an explicit length.
90    ///
91    /// This is needed for zero-child scalar functions and deserialization paths where there is no
92    /// child array to infer the length from.
93    pub fn try_new_with_len(
94        scalar_fn: ScalarFnRef,
95        children: impl IntoIterator<Item = ArrayRef>,
96        len: usize,
97    ) -> VortexResult<Self> {
98        let slots: ArraySlots = children.into_iter().map(Some).collect();
99        Self::try_new_from_slots(scalar_fn, slots, len)
100    }
101
102    #[inline]
103    fn try_new_from_slots(
104        scalar_fn: ScalarFnRef,
105        slots: ArraySlots,
106        len: usize,
107    ) -> VortexResult<Self> {
108        Self::validate_not_literal(&scalar_fn)?;
109        Self::validate_arity(&scalar_fn, slots.len())?;
110        Self::validate_children_len(&slots, len)?;
111
112        let arg_dtypes = Self::arg_dtypes(&slots);
113        let dtype = scalar_fn.return_dtype(&arg_dtypes)?;
114
115        Ok(unsafe { Self::new_from_validated_slots(scalar_fn, dtype, slots, len) })
116    }
117
118    /// # Safety
119    /// The caller must ensure arity and child lengths were validated and
120    /// "dtype" is scalar function's return dtype.
121    #[inline]
122    unsafe fn new_from_validated_slots(
123        scalar_fn: ScalarFnRef,
124        dtype: DType,
125        slots: ArraySlots,
126        len: usize,
127    ) -> Self {
128        let vtable = ScalarFn { id: scalar_fn.id() };
129        let data = ScalarFnData { scalar_fn };
130        unsafe { Array::from_parts_unchecked(ArrayParts::new(vtable, dtype, len, data, slots)) }
131    }
132
133    #[inline]
134    fn arg_dtypes(slots: &[Option<ArrayRef>]) -> SmallVec<[DType; 4]> {
135        let mut arg_dtypes = SmallVec::with_capacity(slots.len());
136        for child in slots.iter().flatten() {
137            arg_dtypes.push(child.dtype().clone());
138        }
139        arg_dtypes
140    }
141
142    #[inline]
143    fn infer_len(slots: &[Option<ArrayRef>]) -> VortexResult<usize> {
144        let Some(child) = slots.first().and_then(Option::as_ref) else {
145            vortex_bail!("ScalarFnArray length cannot be inferred without children");
146        };
147        Ok(child.len())
148    }
149
150    /// A literal is always a [`ConstantArray`], so a ScalarFnArray must never wrap one.
151    ///
152    /// This is not a correctness requirement: a ScalarFnArray that wraps a literal executes
153    /// correctly. The check finds call sites that make a literal node instead of a constant. You
154    /// can relax it if a valid use for a literal ScalarFnArray occurs.
155    ///
156    /// [`ConstantArray`]: crate::arrays::ConstantArray
157    #[inline]
158    pub(super) fn validate_not_literal(scalar_fn: &ScalarFnRef) -> VortexResult<()> {
159        vortex_ensure!(
160            !scalar_fn.is::<Literal>(),
161            "ScalarFnArray cannot wrap a literal, use ConstantArray instead"
162        );
163        Ok(())
164    }
165
166    #[inline]
167    fn validate_arity(scalar_fn: &ScalarFnRef, child_count: usize) -> VortexResult<()> {
168        let arity = scalar_fn.signature().arity();
169        vortex_ensure!(
170            arity.matches(child_count),
171            "ScalarFnArray requires {arity} children, got {child_count}"
172        );
173        Ok(())
174    }
175
176    #[inline]
177    fn validate_children_len(slots: &[Option<ArrayRef>], len: usize) -> VortexResult<()> {
178        for child in slots.iter().flatten() {
179            vortex_ensure!(
180                child.len() == len,
181                "ScalarFnArray must have children equal to the array length"
182            );
183        }
184        Ok(())
185    }
186}