vortex_array/vtable/
dyn_.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5use std::fmt;
6use std::fmt::Debug;
7use std::fmt::Display;
8use std::fmt::Formatter;
9use std::hash::Hash;
10use std::hash::Hasher;
11use std::sync::Arc;
12
13use arcref::ArcRef;
14use vortex_dtype::DType;
15use vortex_error::VortexExpect;
16use vortex_error::VortexResult;
17use vortex_error::vortex_bail;
18use vortex_error::vortex_ensure;
19use vortex_error::vortex_err;
20use vortex_vector::Vector;
21use vortex_vector::VectorOps;
22use vortex_vector::vector_matches_dtype;
23
24use crate::Array;
25use crate::ArrayAdapter;
26use crate::ArrayRef;
27use crate::Canonical;
28use crate::IntoArray;
29use crate::buffer::BufferHandle;
30use crate::executor::ExecutionCtx;
31use crate::serde::ArrayChildren;
32use crate::vtable::EncodeVTable;
33use crate::vtable::VTable;
34
35/// ArrayId is a globally unique name for the array's vtable.
36pub type ArrayId = ArcRef<str>;
37
38/// Dynamically typed trait for invoking array vtables.
39///
40/// This trait contains the internal API for Vortex arrays, allowing us to expose things here
41/// that we do not want to be part of the public [`Array`] trait.
42pub trait DynVTable: 'static + private::Sealed + Send + Sync + Debug {
43    fn as_any(&self) -> &dyn Any;
44
45    fn id(&self) -> ArrayId;
46
47    fn build(
48        &self,
49        dtype: &DType,
50        len: usize,
51        metadata: &[u8],
52        buffers: &[BufferHandle],
53        children: &dyn ArrayChildren,
54    ) -> VortexResult<ArrayRef>;
55    fn with_children(&self, array: &dyn Array, children: Vec<ArrayRef>) -> VortexResult<ArrayRef>;
56    fn encode(&self, input: &Canonical, like: Option<&dyn Array>)
57    -> VortexResult<Option<ArrayRef>>;
58
59    fn reduce(&self, array: &ArrayRef) -> VortexResult<Option<ArrayRef>>;
60    fn reduce_parent(
61        &self,
62        array: &ArrayRef,
63        parent: &ArrayRef,
64        child_idx: usize,
65    ) -> VortexResult<Option<ArrayRef>>;
66
67    fn execute(&self, array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Vector>;
68    fn execute_parent(
69        &self,
70        array: &ArrayRef,
71        parent: &ArrayRef,
72        child_idx: usize,
73        ctx: &mut ExecutionCtx,
74    ) -> VortexResult<Option<Vector>>;
75}
76
77/// Adapter struct used to lift the [`VTable`] trait into an object-safe [`DynVTable`]
78/// implementation.
79///
80/// Since this is a unit struct with `repr(transparent)`, we are able to turn un-adapted array
81/// structs into [`DynVTable`] using some cheeky casting inside [`std::ops::Deref`] and
82/// [`AsRef`]. See the `vtable!` macro for more details.
83#[repr(transparent)]
84pub struct ArrayVTableAdapter<V: VTable>(V);
85impl<V: VTable> DynVTable for ArrayVTableAdapter<V> {
86    fn as_any(&self) -> &dyn Any {
87        self
88    }
89
90    fn id(&self) -> ArrayId {
91        V::id(&self.0)
92    }
93
94    fn build(
95        &self,
96        dtype: &DType,
97        len: usize,
98        metadata_bytes: &[u8],
99        buffers: &[BufferHandle],
100        children: &dyn ArrayChildren,
101    ) -> VortexResult<ArrayRef> {
102        let metadata = V::deserialize(metadata_bytes)?;
103        let array = V::build(&self.0, dtype, len, &metadata, buffers, children)?;
104        assert_eq!(array.len(), len, "Array length mismatch after building");
105        assert_eq!(array.dtype(), dtype, "Array dtype mismatch after building");
106        Ok(array.to_array())
107    }
108
109    fn with_children(&self, array: &dyn Array, children: Vec<ArrayRef>) -> VortexResult<ArrayRef> {
110        let mut array = array.as_::<V>().clone();
111        V::with_children(&mut array, children)?;
112        Ok(array.to_array())
113    }
114
115    fn encode(
116        &self,
117        input: &Canonical,
118        like: Option<&dyn Array>,
119    ) -> VortexResult<Option<ArrayRef>> {
120        let downcast_like = like
121            .map(|like| {
122                like.as_opt::<V>().ok_or_else(|| {
123                    vortex_err!(
124                        "Like array {} does not match requested encoding {}",
125                        like.encoding_id(),
126                        self.id()
127                    )
128                })
129            })
130            .transpose()?;
131
132        let Some(array) =
133            <V::EncodeVTable as EncodeVTable<V>>::encode(&self.0, input, downcast_like)?
134        else {
135            return Ok(None);
136        };
137
138        let input = input.as_ref();
139        if array.len() != input.len() {
140            vortex_bail!(
141                "Array length mismatch after encoding: {} != {}",
142                array.len(),
143                input.len()
144            );
145        }
146        if array.dtype() != input.dtype() {
147            vortex_bail!(
148                "Array dtype mismatch after encoding: {} != {}",
149                array.dtype(),
150                input.dtype()
151            );
152        }
153
154        Ok(Some(array.into_array()))
155    }
156
157    fn reduce(&self, array: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
158        let Some(reduced) = V::reduce(downcast::<V>(array))? else {
159            return Ok(None);
160        };
161        vortex_ensure!(
162            reduced.len() == array.len(),
163            "Reduced array length mismatch from {} to {}",
164            array.display_tree(),
165            reduced.display_tree()
166        );
167        vortex_ensure!(
168            reduced.dtype() == array.dtype(),
169            "Reduced array dtype mismatch from {} to {}",
170            array.display_tree(),
171            reduced.display_tree()
172        );
173        Ok(Some(reduced))
174    }
175
176    fn reduce_parent(
177        &self,
178        array: &ArrayRef,
179        parent: &ArrayRef,
180        child_idx: usize,
181    ) -> VortexResult<Option<ArrayRef>> {
182        let Some(reduced) = V::reduce_parent(downcast::<V>(array), parent, child_idx)? else {
183            return Ok(None);
184        };
185
186        vortex_ensure!(
187            reduced.len() == parent.len(),
188            "Reduced array length mismatch from {} to {}",
189            parent.display_tree(),
190            reduced.display_tree()
191        );
192        vortex_ensure!(
193            reduced.dtype() == parent.dtype(),
194            "Reduced array dtype mismatch from {} to {}",
195            parent.display_tree(),
196            reduced.display_tree()
197        );
198
199        Ok(Some(reduced))
200    }
201
202    fn execute(&self, array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Vector> {
203        let result = V::execute(downcast::<V>(array), ctx)?;
204
205        if cfg!(debug_assertions) {
206            vortex_ensure!(
207                result.len() == array.len(),
208                "Result length mismatch for {}",
209                self.id()
210            );
211            vortex_ensure!(
212                vector_matches_dtype(&result, array.dtype()),
213                "Executed vector dtype mismatch for {}",
214                self.id()
215            );
216        }
217
218        Ok(result)
219    }
220
221    fn execute_parent(
222        &self,
223        array: &ArrayRef,
224        parent: &ArrayRef,
225        child_idx: usize,
226        ctx: &mut ExecutionCtx,
227    ) -> VortexResult<Option<Vector>> {
228        let Some(result) = V::execute_parent(downcast::<V>(array), parent, child_idx, ctx)? else {
229            return Ok(None);
230        };
231
232        vortex_ensure!(
233            result.len() == parent.len(),
234            "Executed parent vector length mismatch"
235        );
236        vortex_ensure!(
237            vector_matches_dtype(&result, parent.dtype()),
238            "Executed parent vector dtype mismatch"
239        );
240
241        Ok(Some(result))
242    }
243}
244
245fn downcast<V: VTable>(array: &ArrayRef) -> &V::Array {
246    array
247        .as_any()
248        .downcast_ref::<ArrayAdapter<V>>()
249        .vortex_expect("Invalid options type for expression")
250        .as_inner()
251}
252
253impl<V: VTable> Debug for ArrayVTableAdapter<V> {
254    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
255        f.debug_struct("Encoding").field("id", &self.id()).finish()
256    }
257}
258
259/// Dynamically typed array vtable.
260#[derive(Clone)]
261pub struct ArrayVTable(ArcRef<dyn DynVTable>);
262
263impl ArrayVTable {
264    /// Returns the underlying vtable API, public only within the crate.
265    pub(crate) fn as_dyn(&self) -> &dyn DynVTable {
266        self.0.as_ref()
267    }
268
269    /// Return the vtable as an Any reference.
270    pub fn as_any(&self) -> &dyn Any {
271        self.0.as_any()
272    }
273
274    /// Creates a new [`ArrayVTable`] from a vtable.
275    ///
276    /// Prefer to use [`Self::new_static`] when possible.
277    pub fn new<V: VTable>(vtable: V) -> Self {
278        Self(ArcRef::new_arc(Arc::new(ArrayVTableAdapter(vtable))))
279    }
280
281    /// Creates a new [`ArrayVTable`] from a static reference to a vtable.
282    pub const fn new_static<V: VTable>(vtable: &'static V) -> Self {
283        // SAFETY: We can safely cast the vtable to a VTableAdapter since it has the same layout.
284        let adapted: &'static ArrayVTableAdapter<V> =
285            unsafe { &*(vtable as *const V as *const ArrayVTableAdapter<V>) };
286        Self(ArcRef::new_ref(adapted as &'static dyn DynVTable))
287    }
288
289    /// Returns the ID of this vtable.
290    pub fn id(&self) -> ArrayId {
291        self.0.id()
292    }
293
294    /// Returns whether this vtable is of a given type.
295    pub fn is<V: VTable>(&self) -> bool {
296        self.0.as_any().is::<V>()
297    }
298
299    /// Encode the canonical array like the given array.
300    pub fn encode(
301        &self,
302        input: &Canonical,
303        like: Option<&dyn Array>,
304    ) -> VortexResult<Option<ArrayRef>> {
305        self.as_dyn().encode(input, like)
306    }
307}
308
309impl PartialEq for ArrayVTable {
310    fn eq(&self, other: &Self) -> bool {
311        self.0.id() == other.0.id()
312    }
313}
314impl Eq for ArrayVTable {}
315
316impl Hash for ArrayVTable {
317    fn hash<H: Hasher>(&self, state: &mut H) {
318        self.0.id().hash(state);
319    }
320}
321
322impl Display for ArrayVTable {
323    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
324        write!(f, "{}", self.as_dyn().id())
325    }
326}
327
328impl Debug for ArrayVTable {
329    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
330        write!(f, "{}", self.as_dyn().id())
331    }
332}
333
334pub trait ArrayVTableExt {
335    /// Wraps the vtable into an `ArrayVTable` by static reference.
336    fn as_vtable(&'static self) -> ArrayVTable;
337
338    /// Wraps the vtable into an `ArrayVTable` by owned reference.
339    fn into_vtable(self) -> ArrayVTable;
340
341    fn to_vtable(&self) -> ArrayVTable
342    where
343        Self: Clone;
344}
345
346// TODO(ngates): deprecate these functions in favor of `ArrayVTable::new` and
347//  `ArrayVTable::new_static`.
348impl<V: VTable> ArrayVTableExt for V {
349    fn as_vtable(&'static self) -> ArrayVTable {
350        ArrayVTable::new_static(self)
351    }
352
353    fn into_vtable(self) -> ArrayVTable {
354        ArrayVTable::new(self)
355    }
356
357    fn to_vtable(&self) -> ArrayVTable
358    where
359        Self: Clone,
360    {
361        ArrayVTable::new(self.clone())
362    }
363}
364
365mod private {
366    use crate::vtable::ArrayVTableAdapter;
367    use crate::vtable::VTable;
368
369    pub trait Sealed {}
370    impl<V: VTable> Sealed for ArrayVTableAdapter<V> {}
371}