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