Skip to main content

vortex_array/array/
erased.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::type_name;
5use std::fmt::Debug;
6use std::fmt::Formatter;
7use std::hash::Hash;
8use std::hash::Hasher;
9use std::ops::Range;
10use std::ptr;
11use std::sync::Arc;
12
13use vortex_buffer::ByteBuffer;
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_error::vortex_ensure;
17use vortex_error::vortex_ensure_eq;
18use vortex_error::vortex_err;
19use vortex_error::vortex_panic;
20use vortex_mask::Mask;
21
22use crate::AnyCanonical;
23use crate::Array;
24use crate::ArrayEq;
25use crate::ArrayHash;
26use crate::ArrayView;
27use crate::Canonical;
28use crate::ExecutionCtx;
29use crate::ExecutionResult;
30use crate::IntoArray;
31use crate::VTable;
32use crate::aggregate_fn::fns::sum::sum;
33use crate::array::ArrayData;
34use crate::array::ArrayId;
35use crate::array::ArrayInner;
36use crate::array::ArraySlots;
37use crate::array::DynArrayData;
38use crate::array::probe::ArrayProbe;
39use crate::array::probe::RepeatedArrayProbe;
40use crate::arrays::Constant;
41use crate::arrays::DictArray;
42use crate::arrays::FilterArray;
43use crate::arrays::SliceArray;
44use crate::buffer::BufferHandle;
45use crate::builders::ArrayBuilder;
46use crate::dtype::DType;
47use crate::expr::stats::Precision;
48use crate::expr::stats::Stat;
49use crate::expr::stats::StatsProviderExt;
50use crate::matcher::Matcher;
51use crate::optimizer::ArrayOptimizer;
52use crate::scalar::Scalar;
53use crate::scalar::ScalarValue;
54use crate::stats::StatsSetRef;
55use crate::validity::Validity;
56
57/// A depth-first pre-order iterator over an Array.
58pub struct DepthFirstArrayIterator {
59    stack: Vec<ArrayRef>,
60}
61
62impl Iterator for DepthFirstArrayIterator {
63    type Item = ArrayRef;
64
65    fn next(&mut self) -> Option<Self::Item> {
66        let next = self.stack.pop()?;
67        for child in next.children().into_iter().rev() {
68            self.stack.push(child);
69        }
70        Some(next)
71    }
72}
73
74/// A reference-counted pointer to a type-erased array.
75///
76/// Wraps `Arc<ArrayInner<dyn DynArrayData>>` — a single 16-byte fat pointer.
77/// Metadata (`len`, `dtype`, `encoding_id`) lives in `ArrayInner::meta` and is
78/// accessed as a normal struct field read — no vtable dispatch, no extra allocation.
79#[derive(Clone)]
80pub struct ArrayRef(Arc<ArrayInner<dyn DynArrayData>>);
81
82impl ArrayRef {
83    /// Create from an `Arc<ArrayInner<dyn DynArrayData>>`.
84    pub(crate) fn from_inner<D: DynArrayData>(inner: Arc<ArrayInner<D>>) -> Self {
85        Self(inner)
86    }
87
88    /// Returns a reference to the `dyn DynArrayData` inside the inner.
89    #[allow(clippy::inline_always)]
90    #[inline(always)]
91    pub(crate) fn dyn_array(&self) -> &dyn DynArrayData {
92        &self.0.data
93    }
94
95    /// Returns a mutable reference to the inner if this is the sole owner.
96    #[allow(clippy::inline_always)]
97    #[inline(always)]
98    pub(crate) fn inner_mut(&mut self) -> Option<&mut ArrayInner<dyn DynArrayData>> {
99        Arc::get_mut(&mut self.0)
100    }
101
102    /// Returns the Arc::as_ptr().addr() of the underlying array.
103    /// This function is used in a couple of places, and we should migrate them to using array_eq.
104    #[doc(hidden)]
105    pub fn addr(&self) -> usize {
106        Arc::as_ptr(&self.0).addr()
107    }
108
109    /// Downcast the inner to a concrete `ArrayInner<ArrayData<V>>`.
110    ///
111    /// Uses the same raw-pointer technique as `Arc::downcast`.
112    #[allow(dead_code)]
113    pub(crate) fn downcast_inner<V: VTable>(self) -> Result<Arc<ArrayInner<ArrayData<V>>>, Self> {
114        // TODO(joe): can we use encoding id here?
115        if self.0.data.as_any().is::<ArrayData<V>>() {
116            Ok(unsafe { self.downcast_inner_unchecked() })
117        } else {
118            Err(self)
119        }
120    }
121
122    /// Downcast without a runtime type check.
123    ///
124    /// # Safety
125    /// The caller must guarantee the concrete type behind `dyn DynArrayData` is `ArrayData<V>`.
126    #[allow(clippy::inline_always)]
127    #[inline(always)]
128    pub(crate) unsafe fn downcast_inner_unchecked<V: VTable>(
129        self,
130    ) -> Arc<ArrayInner<ArrayData<V>>> {
131        debug_assert!(self.0.data.as_any().is::<ArrayData<V>>());
132        // Recover the original concrete Arc. The fat pointer's data pointer is the
133        // same allocation that was originally `Arc<ArrayInner<ArrayData<V>>>` before
134        // unsized coercion to `Arc<ArrayInner<dyn DynArrayData>>`.
135        let raw = Arc::into_raw(self.0);
136        // # Safety all arrays are constructed in this way and type aliased.
137        unsafe { Arc::from_raw(raw.cast::<ArrayInner<ArrayData<V>>>()) }
138    }
139
140    /// Returns true if the two ArrayRefs point to the same allocation.
141    pub fn ptr_eq(this: &ArrayRef, other: &ArrayRef) -> bool {
142        Arc::ptr_eq(&this.0, &other.0)
143    }
144}
145
146impl Debug for ArrayRef {
147    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148        f.debug_struct("Array")
149            .field("encoding", &self.0.encoding_id)
150            .field("dtype", &self.0.dtype)
151            .field("len", &self.0.len)
152            .field("data", &self.0.data)
153            .finish()
154    }
155}
156
157impl ArrayHash for ArrayRef {
158    fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: crate::EqMode) {
159        self.0.len.hash(state);
160        self.0.dtype.hash(state);
161        self.0.encoding_id.hash(state);
162        self.0.slots.len().hash(state);
163        for slot in &self.0.slots {
164            slot.array_hash(state, accuracy);
165        }
166        self.0
167            .data
168            .dyn_array_hash(state as &mut dyn Hasher, accuracy);
169    }
170}
171
172impl ArrayEq for ArrayRef {
173    fn array_eq(&self, other: &Self, accuracy: crate::EqMode) -> bool {
174        self.0.len == other.0.len
175            && self.0.dtype == other.0.dtype
176            && self.0.encoding_id == other.0.encoding_id
177            && self.0.slots.len() == other.0.slots.len()
178            && self
179                .0
180                .slots
181                .iter()
182                .zip(other.0.slots.iter())
183                .all(|(slot, other_slot)| slot.array_eq(other_slot, accuracy))
184            && self.0.data.dyn_array_eq(other, accuracy)
185    }
186}
187impl ArrayRef {
188    /// Returns the length of the array.
189    #[inline]
190    pub fn len(&self) -> usize {
191        self.0.len
192    }
193
194    /// Returns whether the array is empty (has zero rows).
195    #[inline]
196    pub fn is_empty(&self) -> bool {
197        self.0.len == 0
198    }
199
200    /// Returns the logical Vortex [`DType`] of the array.
201    #[inline]
202    pub fn dtype(&self) -> &DType {
203        &self.0.dtype
204    }
205
206    /// Returns the encoding ID of the array.
207    #[inline]
208    pub fn encoding_id(&self) -> ArrayId {
209        self.0.encoding_id
210    }
211
212    /// Performs a constant-time slice of the array.
213    pub fn slice(&self, range: Range<usize>) -> VortexResult<ArrayRef> {
214        let len = self.len();
215        let start = range.start;
216        let stop = range.end;
217
218        if start == 0 && stop == len {
219            return Ok(self.clone());
220        }
221
222        vortex_ensure!(start <= len, "OutOfBounds: start {start} > length {}", len);
223        vortex_ensure!(stop <= len, "OutOfBounds: stop {stop} > length {}", len);
224
225        vortex_ensure!(start <= stop, "start ({start}) must be <= stop ({stop})");
226
227        if start == stop {
228            return Ok(Canonical::empty(self.dtype()).into_array());
229        }
230
231        let sliced = SliceArray::try_new(self.clone(), range)?
232            .into_array()
233            .optimize()?;
234
235        // Propagate some stats from the original array to the sliced array.
236        if !sliced.is::<Constant>() {
237            self.statistics().with_iter(|iter| {
238                sliced.statistics().inherit(iter.filter(|(stat, value)| {
239                    matches!(
240                        stat,
241                        Stat::IsConstant | Stat::IsSorted | Stat::IsStrictSorted
242                    ) && value
243                        .as_ref()
244                        .as_exact()
245                        .is_some_and(|v| matches!(v, ScalarValue::Bool(true)))
246                }));
247            });
248        }
249
250        Ok(sliced)
251    }
252
253    /// Wraps the array in a [`FilterArray`] such that it is logically filtered by the given mask.
254    pub fn filter(&self, mask: Mask) -> VortexResult<ArrayRef> {
255        FilterArray::try_new(self.clone(), mask)?
256            .into_array()
257            .optimize()
258    }
259
260    /// Wraps the array in a [`DictArray`] such that it is logically taken by the given indices.
261    pub fn take(&self, indices: ArrayRef) -> VortexResult<ArrayRef> {
262        DictArray::try_new(indices, self.clone())?
263            .into_array()
264            .optimize()
265    }
266
267    /// Execute the array to extract a scalar at the given index.
268    ///
269    /// A one-off read; the same as `self.probe().execute_scalar(index, ctx)`.
270    // TODO(joe): deprecate this in favour of `probe()`.
271    #[inline]
272    pub fn execute_scalar(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<Scalar> {
273        self.probe().execute_scalar(index, ctx)
274    }
275
276    /// A one-off row accessor over this array. It borrows the handle and retains nothing; for
277    /// many reads of the same array use [`Self::repeated_probe`].
278    ///
279    /// ```
280    /// use vortex_array::{IntoArray, VortexSessionExecute};
281    /// use vortex_array::arrays::PrimitiveArray;
282    ///
283    /// let array = PrimitiveArray::from_iter([10i32, 20, 30]).into_array();
284    /// let mut ctx = vortex_array::array_session().create_execution_ctx();
285    /// assert_eq!(array.probe().execute_scalar(2, &mut ctx)?, 30i32.into());
286    /// # Ok::<(), vortex_error::VortexError>(())
287    /// ```
288    #[inline]
289    pub fn probe(&self) -> ArrayProbe<'_> {
290        ArrayProbe::Once(self)
291    }
292
293    /// A row accessor that owns a handle to this array and keeps encoding state, its validity
294    /// probe and child probes between reads.
295    ///
296    /// ```
297    /// use vortex_array::{IntoArray, VortexSessionExecute};
298    /// use vortex_array::arrays::PrimitiveArray;
299    ///
300    /// let array = PrimitiveArray::from_iter([10i32, 20, 30]).into_array();
301    /// let mut ctx = vortex_array::array_session().create_execution_ctx();
302    /// let mut probe = array.repeated_probe();
303    /// assert_eq!(probe.execute_scalar(2, &mut ctx)?, 30i32.into());
304    /// assert_eq!(probe.execute_scalar(0, &mut ctx)?, 10i32.into());
305    /// # Ok::<(), vortex_error::VortexError>(())
306    /// ```
307    pub fn repeated_probe(&self) -> RepeatedArrayProbe {
308        RepeatedArrayProbe::new(self.clone())
309    }
310
311    /// Returns whether the item at `index` is valid.
312    ///
313    /// A one-off read; the same as `self.probe().execute_is_valid(index, ctx)`.
314    // TODO(joe): deprecate this in favour of `probe/repeated_probe()`.
315    #[inline]
316    pub fn is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
317        self.probe().execute_is_valid(index, ctx)
318    }
319
320    /// Returns whether the item at `index` is invalid.
321    // TODO(joe): deprecate this.
322    #[inline]
323    pub fn is_invalid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
324        Ok(!self.is_valid(index, ctx)?)
325    }
326
327    /// Returns whether all items in the array are valid.
328    pub fn all_valid(&self, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
329        if self.is_empty() {
330            return Ok(true);
331        }
332
333        match self.validity()? {
334            Validity::NonNullable | Validity::AllValid => Ok(true),
335            Validity::AllInvalid => Ok(false),
336            Validity::Array(a) => Ok(a.statistics().compute_min::<bool>(ctx).unwrap_or(false)),
337        }
338    }
339
340    /// Returns whether the array is all invalid.
341    pub fn all_invalid(&self, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
342        if self.is_empty() {
343            return Ok(true);
344        }
345
346        match self.validity()? {
347            Validity::NonNullable | Validity::AllValid => Ok(false),
348            Validity::AllInvalid => Ok(true),
349            Validity::Array(a) => Ok(!a.statistics().compute_max::<bool>(ctx).unwrap_or(true)),
350        }
351    }
352
353    /// Returns the number of valid elements in the array.
354    pub fn valid_count(&self, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
355        let len = self.len();
356        if let Precision::Exact(invalid_count) = self.statistics().get_as::<usize>(Stat::NullCount)
357        {
358            return Ok(len - invalid_count);
359        }
360
361        let count = match self.validity()? {
362            Validity::NonNullable | Validity::AllValid => len,
363            Validity::AllInvalid => 0,
364            Validity::Array(a) => {
365                let array_sum = sum(&a, ctx)?;
366                array_sum
367                    .as_primitive()
368                    .as_::<usize>()
369                    .ok_or_else(|| vortex_err!("sum of validity array is null"))?
370            }
371        };
372        vortex_ensure!(count <= len, "Valid count exceeds array length");
373
374        self.statistics()
375            .set(Stat::NullCount, Precision::exact(len - count));
376
377        Ok(count)
378    }
379
380    /// Returns the number of invalid elements in the array.
381    pub fn invalid_count(&self, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
382        Ok(self.len() - self.valid_count(ctx)?)
383    }
384
385    /// Returns the [`Validity`] of the array.
386    pub fn validity(&self) -> VortexResult<Validity> {
387        self.0.data.validity(self)
388    }
389
390    /// Writes the array into the canonical builder.
391    pub fn append_to_builder(
392        &self,
393        builder: &mut dyn ArrayBuilder,
394        ctx: &mut ExecutionCtx,
395    ) -> VortexResult<()> {
396        self.0.data.append_to_builder(self, builder, ctx)
397    }
398
399    /// Returns the statistics of the array.
400    pub fn statistics(&self) -> StatsSetRef<'_> {
401        self.0.stats.to_ref(self)
402    }
403
404    /// Does the array match the given matcher.
405    #[inline]
406    pub fn is<M: Matcher>(&self) -> bool {
407        M::matches(self)
408    }
409
410    /// Returns the array downcast by the given matcher.
411    #[inline]
412    pub fn as_<M: Matcher>(&self) -> M::Match<'_> {
413        self.as_opt::<M>().vortex_expect("Failed to downcast")
414    }
415
416    /// Returns the array downcast by the given matcher.
417    #[inline]
418    pub fn as_opt<M: Matcher>(&self) -> Option<M::Match<'_>> {
419        M::try_match(self)
420    }
421
422    /// Returns the array downcast to the given `Array<V>` as an owned typed handle.
423    pub fn try_downcast<V: VTable>(self) -> Result<Array<V>, ArrayRef> {
424        Array::<V>::try_from_array_ref(self)
425    }
426
427    /// Returns the array downcast to the given `Array<V>` as an owned typed handle.
428    ///
429    /// # Panics
430    ///
431    /// Panics if the array is not of the given type.
432    pub fn downcast<V: VTable>(self) -> Array<V> {
433        Self::try_downcast(self)
434            .unwrap_or_else(|_| vortex_panic!("Failed to downcast to {}", type_name::<V>()))
435    }
436
437    /// Returns a reference to the typed `ArrayData<V>` if this array matches the given vtable type.
438    pub fn as_typed<V: VTable>(&self) -> Option<ArrayView<'_, V>> {
439        let inner = self.0.data.as_any().downcast_ref::<ArrayData<V>>()?;
440        Some(unsafe { ArrayView::new_unchecked(self, &inner.data) })
441    }
442
443    /// Returns a typed view without a runtime type check.
444    ///
445    /// # Safety
446    /// The caller must guarantee the concrete type behind `dyn DynArrayData` is `ArrayData<V>`.
447    #[inline]
448    pub(crate) unsafe fn as_typed_unchecked<V: VTable>(&self) -> ArrayView<'_, V> {
449        debug_assert!(self.is::<V>());
450        // SAFETY: the caller guarantees the concrete type, so the thin data pointer of the
451        // `dyn DynArrayData` points at an `ArrayData<V>`.
452        let inner = unsafe { &*ptr::from_ref(self.dyn_array()).cast::<ArrayData<V>>() };
453        // SAFETY: `inner` is the typed data stored inside `self`.
454        unsafe { ArrayView::new_unchecked(self, &inner.data) }
455    }
456
457    /// Returns the constant scalar if this is a constant array.
458    pub fn as_constant(&self) -> Option<Scalar> {
459        self.as_opt::<Constant>().map(|a| a.scalar().clone())
460    }
461
462    /// Total size of the array in bytes, including all children and buffers.
463    pub fn nbytes(&self) -> u64 {
464        let mut nbytes = 0;
465        for array in self.depth_first_traversal() {
466            for buffer in array.buffers() {
467                nbytes += buffer.len() as u64;
468            }
469        }
470        nbytes
471    }
472
473    /// Whether the array is of a canonical encoding.
474    pub fn is_canonical(&self) -> bool {
475        self.is::<AnyCanonical>()
476    }
477
478    /// Returns a new array with the slot at `slot_idx` replaced by `replacement`.
479    ///
480    /// This is only valid for physical rewrites: the replacement must have the same logical
481    /// `DType` and `len` as the existing slot.
482    ///
483    /// # Safety
484    ///
485    /// If this returns `Ok`, the caller must guarantee that the replacement slot represents the
486    /// same logical values as the original slot. Only the physical representation may change.
487    /// Existing parent statistics are preserved and must remain valid.
488    ///
489    /// Takes ownership to allow in-place mutation when the refcount is 1.
490    pub unsafe fn with_slot(
491        self,
492        slot_idx: usize,
493        replacement: ArrayRef,
494    ) -> VortexResult<ArrayRef> {
495        let mut slots: ArraySlots = self.slots().iter().cloned().collect();
496        let nslots = slots.len();
497        vortex_ensure!(
498            slot_idx < nslots,
499            "slot index {} out of bounds for array with {} slots",
500            slot_idx,
501            nslots
502        );
503        let existing = slots[slot_idx]
504            .as_ref()
505            .vortex_expect("with_slot cannot replace an absent slot");
506        vortex_ensure_eq!(
507            existing.dtype(),
508            replacement.dtype(),
509            "slot {} dtype changed during physical rewrite",
510            slot_idx
511        );
512        vortex_ensure_eq!(
513            existing.len(),
514            replacement.len(),
515            "slot {} len changed during physical rewrite",
516            slot_idx
517        );
518        slots[slot_idx] = Some(replacement);
519        // SAFETY: upheld by the caller of this unsafe API.
520        unsafe { self.with_slots(slots) }
521    }
522
523    /// Take a slot for executor-owned physical rewrites.
524    ///
525    /// On return the produced parent has the taken slot set to `None`
526    /// callers must put the slot back (typically via [`Self::put_slot_unchecked`]) before the parent is
527    /// returned from the execution loop.
528    ///
529    /// When the `Arc` was shared this allocates a fresh parent.
530    ///
531    /// # Safety
532    /// The caller must put back a slot with the same logical dtype and length before exposing the
533    /// parent array, and must only use this for physical rewrites.
534    pub(crate) unsafe fn take_slot_unchecked(
535        mut self,
536        slot_idx: usize,
537    ) -> VortexResult<(ArrayRef, ArrayRef)> {
538        if let Some(inner) = Arc::get_mut(&mut self.0) {
539            let child = inner.slots[slot_idx]
540                .take()
541                .vortex_expect("take_slot_unchecked cannot take an absent slot");
542            return Ok((self, child));
543        }
544
545        // Arc is shared: clone the child out and build a fresh parent with slot_idx = None,
546        // bypassing encoding-level validation so the absent slot does not panic `V::validate`.
547        let child = self.slots()[slot_idx]
548            .as_ref()
549            .vortex_expect("take_slot_unchecked cannot take an absent slot")
550            .clone();
551
552        let mut new_slots: ArraySlots = self.slots().iter().cloned().collect();
553        new_slots[slot_idx] = None;
554
555        // SAFETY: ensured by the caller — the None slot is either put back or driven to completion
556        // via the builder path before the parent escapes the executor.
557        let new_parent = unsafe { self.0.data.with_slots_unchecked(&self, new_slots) };
558        Ok((new_parent, child))
559    }
560
561    /// Puts an array into `slot_idx` by either, cloning the inner array if the Arc is not exclusive
562    /// or replacing the slot in this `ArrayRef`.
563    /// This is the mirror of [`Self::take_slot_unchecked`].
564    ///
565    /// # Safety
566    /// The replacement must have the same logical dtype and length as the taken slot, and this
567    /// must only be used for physical rewrites.
568    pub(crate) unsafe fn put_slot_unchecked(
569        mut self,
570        slot_idx: usize,
571        replacement: ArrayRef,
572    ) -> VortexResult<ArrayRef> {
573        if let Some(inner) = Arc::get_mut(&mut self.0) {
574            inner.slots[slot_idx] = Some(replacement);
575            return Ok(self);
576        }
577
578        let mut slots: ArraySlots = self.slots().iter().cloned().collect();
579        slots[slot_idx] = Some(replacement);
580        self.0.data.with_slots(&self, slots)
581    }
582
583    /// Returns a new array with the provided slots.
584    ///
585    /// This is only valid for physical rewrites: slot count, presence, logical `DType`, and
586    /// logical `len` must remain unchanged.
587    ///
588    /// # Safety
589    ///
590    /// If this returns `Ok`, the caller must guarantee that each replacement slot represents the
591    /// same logical values as the original slot. Only physical representation may change. Existing
592    /// parent statistics are preserved and must remain valid.
593    pub unsafe fn with_slots(self, slots: ArraySlots) -> VortexResult<ArrayRef> {
594        let old_slots = self.slots();
595        vortex_ensure_eq!(
596            old_slots.len(),
597            slots.len(),
598            "slot count changed during physical rewrite"
599        );
600        for (idx, (old_slot, new_slot)) in old_slots.iter().zip(slots.iter()).enumerate() {
601            vortex_ensure_eq!(
602                old_slot.is_some(),
603                new_slot.is_some(),
604                "slot {} presence changed during physical rewrite",
605                idx
606            );
607            if let (Some(old_slot), Some(new_slot)) = (old_slot.as_ref(), new_slot.as_ref()) {
608                vortex_ensure_eq!(
609                    old_slot.dtype(),
610                    new_slot.dtype(),
611                    "slot {} dtype changed during physical rewrite",
612                    idx
613                );
614                vortex_ensure_eq!(
615                    old_slot.len(),
616                    new_slot.len(),
617                    "slot {} len changed during physical rewrite",
618                    idx
619                );
620            }
621        }
622        self.0.data.with_slots(&self, slots)
623    }
624
625    /// Returns a new array with the provided top-level buffer handles.
626    ///
627    /// This is only valid for physical rewrites: buffer count, logical `DType`, logical `len`, and
628    /// child slots must remain unchanged. Encoding-specific validation checks buffer shape,
629    /// alignment, and metadata consistency.
630    ///
631    /// # Safety
632    ///
633    /// If this returns `Ok`, the caller must guarantee that the replacement buffers represent the
634    /// same logical values as the original buffers. Only the buffer handle implementation,
635    /// placement, or backing storage may change. Existing statistics are preserved and must remain
636    /// valid.
637    pub unsafe fn with_buffers(
638        self,
639        buffers: impl IntoIterator<Item = BufferHandle>,
640    ) -> VortexResult<ArrayRef> {
641        let buffers = buffers.into_iter().collect::<Vec<_>>();
642        let nbuffers = self.nbuffers();
643        vortex_ensure_eq!(
644            nbuffers,
645            buffers.len(),
646            "buffer count changed during physical rewrite"
647        );
648        for (idx, (old_buffer, new_buffer)) in self
649            .buffer_handles()
650            .into_iter()
651            .zip(buffers.iter())
652            .enumerate()
653        {
654            vortex_ensure_eq!(
655                old_buffer.len(),
656                new_buffer.len(),
657                "buffer {} length changed during physical rewrite",
658                idx
659            );
660        }
661        self.0.data.with_buffers(&self, buffers)
662    }
663
664    pub fn reduce(&self) -> VortexResult<Option<ArrayRef>> {
665        self.0.data.reduce(self)
666    }
667
668    pub fn reduce_parent(
669        &self,
670        parent: &ArrayRef,
671        child_idx: usize,
672    ) -> VortexResult<Option<ArrayRef>> {
673        self.0.data.reduce_parent(self, parent, child_idx)
674    }
675
676    pub(crate) fn execute_encoding(self, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
677        let inner = Arc::as_ptr(&self.0);
678        // SAFETY: the Arc outlives the DynArrayData function call
679        unsafe { (&*inner).data.execute(self, ctx) }
680    }
681
682    /// Execute a single encoding step without applying `Done`-result postconditions.
683    ///
684    /// This is for the iterative executor only. It may operate on suspended executor-private
685    /// arrays whose slots temporarily contain `None`, so the executor itself must interpret
686    /// `Done`, enforce any `len`/`dtype` invariants, and transfer statistics.
687    pub(crate) fn execute_encoding_unchecked(
688        self,
689        ctx: &mut ExecutionCtx,
690    ) -> VortexResult<ExecutionResult> {
691        let inner = Arc::as_ptr(&self.0);
692        // SAFETY: `inner` points at the allocation owned by `self.0`. `self` stays alive for the
693        // duration of the call, so the pointee remains valid. Avoiding an extra `Arc` clone here
694        // preserves uniqueness so execute-time metadata cursors can use `Arc::get_mut`.
695        unsafe { (&*inner).data.execute_unchecked(self, ctx) }
696    }
697
698    // ArrayVisitor delegation methods
699
700    /// Returns an iterator over the children of the array: its non-None slots in order.
701    pub fn children_iter(&self) -> impl Iterator<Item = &ArrayRef> {
702        self.0.slots.iter().filter_map(|s| s.as_ref())
703    }
704
705    /// Returns the children of the array.
706    pub fn children(&self) -> Vec<ArrayRef> {
707        self.children_iter().cloned().collect()
708    }
709
710    /// Returns the number of children of the array.
711    pub fn nchildren(&self) -> usize {
712        self.children_iter().count()
713    }
714
715    /// Returns the nth child of the array without allocating a Vec.
716    ///
717    /// Returns `None` if the index is out of bounds.
718    pub fn nth_child(&self, idx: usize) -> Option<&ArrayRef> {
719        self.children_iter().nth(idx)
720    }
721
722    /// Returns the names of the children of the array: the slot names of the non-None slots
723    /// in order.
724    pub fn children_names(&self) -> Vec<String> {
725        self.0
726            .slots
727            .iter()
728            .enumerate()
729            .filter(|(_, s)| s.is_some())
730            .map(|(slot_idx, _)| self.slot_name(slot_idx))
731            .collect()
732    }
733
734    /// Returns the array's children with their names.
735    pub fn named_children(&self) -> Vec<(String, ArrayRef)> {
736        self.children_names()
737            .into_iter()
738            .zip(self.children_iter().cloned())
739            .collect()
740    }
741
742    /// Returns the data buffers of the array.
743    pub fn buffers(&self) -> Vec<ByteBuffer> {
744        self.0.data.buffers(self)
745    }
746
747    /// Returns the buffer handles of the array.
748    pub fn buffer_handles(&self) -> Vec<BufferHandle> {
749        self.0.data.buffer_handles(self)
750    }
751
752    /// Returns the names of the buffers of the array.
753    pub fn buffer_names(&self) -> Vec<String> {
754        self.0.data.buffer_names(self)
755    }
756
757    /// Returns the array's buffers with their names.
758    pub fn named_buffers(&self) -> Vec<(String, BufferHandle)> {
759        self.0.data.named_buffers(self)
760    }
761
762    /// Returns the number of data buffers of the array.
763    pub fn nbuffers(&self) -> usize {
764        self.0.data.nbuffers(self)
765    }
766
767    /// Returns the slots of the array.
768    pub fn slots(&self) -> &[Option<ArrayRef>] {
769        &self.0.slots
770    }
771
772    /// Returns the name of the slot at the given index.
773    pub fn slot_name(&self, idx: usize) -> String {
774        self.0.data.slot_name(self, idx)
775    }
776
777    /// Formats a human-readable metadata description.
778    pub fn metadata_fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
779        self.0.data.metadata_fmt(f)
780    }
781
782    /// Returns whether all buffers are host-resident.
783    pub fn is_host(&self) -> bool {
784        for array in self.depth_first_traversal() {
785            if !array.buffer_handles().iter().all(BufferHandle::is_on_host) {
786                return false;
787            }
788        }
789        true
790    }
791
792    // ArrayVisitorExt delegation methods
793
794    /// Count the number of buffers encoded by self and all child arrays.
795    pub fn nbuffers_recursive(&self) -> usize {
796        self.children()
797            .iter()
798            .map(|c| c.nbuffers_recursive())
799            .sum::<usize>()
800            + self.nbuffers()
801    }
802
803    /// Depth-first traversal of the array and its children.
804    pub fn depth_first_traversal(&self) -> DepthFirstArrayIterator {
805        DepthFirstArrayIterator {
806            stack: vec![self.clone()],
807        }
808    }
809}
810
811impl IntoArray for ArrayRef {
812    #[allow(clippy::inline_always)]
813    #[inline(always)]
814    fn into_array(self) -> ArrayRef {
815        self
816    }
817}
818
819impl<V: VTable> Matcher for V {
820    type Match<'a> = ArrayView<'a, V>;
821
822    #[inline]
823    fn matches(array: &ArrayRef) -> bool {
824        array.0.data.as_any().is::<ArrayData<V>>()
825    }
826
827    #[inline]
828    fn try_match(array: &'_ ArrayRef) -> Option<ArrayView<'_, V>> {
829        let inner = array.0.data.as_any().downcast_ref::<ArrayData<V>>()?;
830        // # Safety checked by `downcast_ref`.
831        Some(unsafe { ArrayView::new_unchecked(array, &inner.data) })
832    }
833}