From 8146a9933593bf18bc017433b72d239b998d5617 Mon Sep 17 00:00:00 2001 From: Joe Isaacs Date: Fri, 11 Sep 2026 14:26:57 +0100 Subject: [PATCH 1/9] feat(array): add scalar probes with lazy child contexts Signed-off-by: Joe Isaacs --- encodings/alp/src/alp/ops.rs | 2 + encodings/alp/src/alp_rd/ops.rs | 2 + encodings/bytebool/src/array.rs | 2 + encodings/datetime-parts/src/ops.rs | 2 + .../src/decimal_byte_parts/mod.rs | 2 + .../src/bitpacking/vtable/operations.rs | 2 + .../fastlanes/src/delta/vtable/operations.rs | 2 + .../fastlanes/src/for/vtable/operations.rs | 2 + .../fastlanes/src/rle/vtable/operations.rs | 2 + encodings/fastlanes/src/transposed_bool.rs | 2 + encodings/fsst/src/ops.rs | 2 + encodings/onpair/src/ops.rs | 2 + encodings/parquet-variant/src/operations.rs | 2 + encodings/pco/src/array.rs | 2 + encodings/runend/src/ops.rs | 2 + encodings/sequence/src/array.rs | 2 + encodings/sparse/src/ops.rs | 2 + encodings/zigzag/src/array.rs | 2 + encodings/zstd/src/array.rs | 2 + encodings/zstd/src/zstd_buffers.rs | 2 + vortex-array/PROBE_DESIGN.md | 130 ++++++++++ vortex-array/src/array/mod.rs | 38 +++ vortex-array/src/array/probe.rs | 241 ++++++++++++++++++ vortex-array/src/array/probe/tests.rs | 220 ++++++++++++++++ vortex-array/src/array/vtable/operations.rs | 27 ++ .../src/arrays/bool/vtable/operations.rs | 2 + .../src/arrays/chunked/vtable/operations.rs | 2 + .../src/arrays/constant/vtable/operations.rs | 2 + .../src/arrays/decimal/vtable/operations.rs | 2 + .../src/arrays/dict/vtable/operations.rs | 2 + .../src/arrays/extension/vtable/operations.rs | 2 + vortex-array/src/arrays/filter/vtable.rs | 2 + .../fixed_size_list/vtable/operations.rs | 2 + vortex-array/src/arrays/interleave/mod.rs | 2 + .../src/arrays/list/vtable/operations.rs | 2 + .../src/arrays/listview/vtable/operations.rs | 2 + .../src/arrays/map/vtable/operations.rs | 2 + .../src/arrays/masked/vtable/operations.rs | 2 + vortex-array/src/arrays/null/mod.rs | 2 + .../src/arrays/patched/vtable/operations.rs | 2 + .../src/arrays/piecewise_sequence/vtable.rs | 2 + .../src/arrays/primitive/vtable/operations.rs | 2 + .../src/arrays/scalar_fn/vtable/operations.rs | 2 + vortex-array/src/arrays/shared/vtable.rs | 2 + vortex-array/src/arrays/slice/vtable.rs | 2 + .../src/arrays/struct_/vtable/operations.rs | 2 + .../src/arrays/union/vtable/operations.rs | 2 + .../src/arrays/varbin/vtable/operations.rs | 2 + .../arrays/varbinview/vtable/operations.rs | 2 + .../src/arrays/variant/vtable/operations.rs | 2 + vortex-python/src/arrays/py/vtable.rs | 2 + 51 files changed, 748 insertions(+) create mode 100644 vortex-array/PROBE_DESIGN.md create mode 100644 vortex-array/src/array/probe.rs create mode 100644 vortex-array/src/array/probe/tests.rs diff --git a/encodings/alp/src/alp/ops.rs b/encodings/alp/src/alp/ops.rs index a8850744056..8b80ee9a875 100644 --- a/encodings/alp/src/alp/ops.rs +++ b/encodings/alp/src/alp/ops.rs @@ -15,6 +15,8 @@ use crate::ALPFloat; use crate::match_each_alp_float_ptype; impl OperationsVTable for ALP { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, ALP>, index: usize, diff --git a/encodings/alp/src/alp_rd/ops.rs b/encodings/alp/src/alp_rd/ops.rs index edb2fb21186..40749c51557 100644 --- a/encodings/alp/src/alp_rd/ops.rs +++ b/encodings/alp/src/alp_rd/ops.rs @@ -14,6 +14,8 @@ use crate::ALPRDArrayExt; use crate::ALPRDArraySlotsExt; impl OperationsVTable for ALPRD { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, ALPRD>, index: usize, diff --git a/encodings/bytebool/src/array.rs b/encodings/bytebool/src/array.rs index faa1fea81fa..1236ebdd5e7 100644 --- a/encodings/bytebool/src/array.rs +++ b/encodings/bytebool/src/array.rs @@ -312,6 +312,8 @@ impl ValidityVTable for ByteBool { } impl OperationsVTable for ByteBool { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, ByteBool>, index: usize, diff --git a/encodings/datetime-parts/src/ops.rs b/encodings/datetime-parts/src/ops.rs index d99e55c7542..e3be4a1b0ff 100644 --- a/encodings/datetime-parts/src/ops.rs +++ b/encodings/datetime-parts/src/ops.rs @@ -17,6 +17,8 @@ use crate::timestamp; use crate::timestamp::TimestampParts; impl OperationsVTable for DateTimeParts { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, DateTimeParts>, index: usize, diff --git a/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs b/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs index d5b0024f5b7..62c0eea25cb 100644 --- a/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs +++ b/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs @@ -289,6 +289,8 @@ fn to_canonical_decimal( } impl OperationsVTable for DecimalByteParts { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, DecimalByteParts>, index: usize, diff --git a/encodings/fastlanes/src/bitpacking/vtable/operations.rs b/encodings/fastlanes/src/bitpacking/vtable/operations.rs index e14b27323c1..4c4be1e746f 100644 --- a/encodings/fastlanes/src/bitpacking/vtable/operations.rs +++ b/encodings/fastlanes/src/bitpacking/vtable/operations.rs @@ -11,6 +11,8 @@ use crate::BitPacked; use crate::bitpack_decompress; use crate::bitpacking::array::BitPackedArrayExt; impl OperationsVTable for BitPacked { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, BitPacked>, index: usize, diff --git a/encodings/fastlanes/src/delta/vtable/operations.rs b/encodings/fastlanes/src/delta/vtable/operations.rs index 7ed57a0886d..999fad1b33c 100644 --- a/encodings/fastlanes/src/delta/vtable/operations.rs +++ b/encodings/fastlanes/src/delta/vtable/operations.rs @@ -11,6 +11,8 @@ use vortex_error::VortexResult; use super::Delta; impl OperationsVTable for Delta { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Delta>, index: usize, diff --git a/encodings/fastlanes/src/for/vtable/operations.rs b/encodings/fastlanes/src/for/vtable/operations.rs index 36dac998cbe..3df84a704a9 100644 --- a/encodings/fastlanes/src/for/vtable/operations.rs +++ b/encodings/fastlanes/src/for/vtable/operations.rs @@ -13,6 +13,8 @@ use super::FoR; use crate::r#for::array::FoRArrayExt; use crate::r#for::array::FoRArraySlotsExt; impl OperationsVTable for FoR { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, FoR>, index: usize, diff --git a/encodings/fastlanes/src/rle/vtable/operations.rs b/encodings/fastlanes/src/rle/vtable/operations.rs index ca4d2d39545..8045fc550cc 100644 --- a/encodings/fastlanes/src/rle/vtable/operations.rs +++ b/encodings/fastlanes/src/rle/vtable/operations.rs @@ -14,6 +14,8 @@ use crate::rle::RLEArrayExt; use crate::rle::RLEArraySlotsExt; impl OperationsVTable for RLE { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, RLE>, index: usize, diff --git a/encodings/fastlanes/src/transposed_bool.rs b/encodings/fastlanes/src/transposed_bool.rs index efb3443cdfc..3acd47772b8 100644 --- a/encodings/fastlanes/src/transposed_bool.rs +++ b/encodings/fastlanes/src/transposed_bool.rs @@ -256,6 +256,8 @@ impl VTable for TransposedBool { } impl OperationsVTable for TransposedBool { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, TransposedBool>, index: usize, diff --git a/encodings/fsst/src/ops.rs b/encodings/fsst/src/ops.rs index b630508ed9e..615dc78d6db 100644 --- a/encodings/fsst/src/ops.rs +++ b/encodings/fsst/src/ops.rs @@ -14,6 +14,8 @@ use crate::FSST; use crate::FSSTArrayExt; impl OperationsVTable for FSST { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, FSST>, index: usize, diff --git a/encodings/onpair/src/ops.rs b/encodings/onpair/src/ops.rs index 728e5a0e6f2..a78df1d4b7d 100644 --- a/encodings/onpair/src/ops.rs +++ b/encodings/onpair/src/ops.rs @@ -18,6 +18,8 @@ use crate::decode::code_boundary_at; use crate::decode::collect_widened; impl OperationsVTable for OnPair { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, OnPair>, index: usize, diff --git a/encodings/parquet-variant/src/operations.rs b/encodings/parquet-variant/src/operations.rs index 8517df27f22..b8579516228 100644 --- a/encodings/parquet-variant/src/operations.rs +++ b/encodings/parquet-variant/src/operations.rs @@ -31,6 +31,8 @@ use crate::ParquetVariantArraySlotsExt; use crate::vtable::ParquetVariant; impl OperationsVTable for ParquetVariant { + type ProbeState<'a> = (); + /// Resolves one row according to the Parquet Variant shredding rules. /// /// For valid data, a row with both `value` and struct `typed_value` is a partially diff --git a/encodings/pco/src/array.rs b/encodings/pco/src/array.rs index 44a6a8e4045..48939f9efe8 100644 --- a/encodings/pco/src/array.rs +++ b/encodings/pco/src/array.rs @@ -778,6 +778,8 @@ impl ValidityVTable for Pco { } impl OperationsVTable for Pco { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Pco>, index: usize, diff --git a/encodings/runend/src/ops.rs b/encodings/runend/src/ops.rs index e2c2e3fc99b..14acef6c3e1 100644 --- a/encodings/runend/src/ops.rs +++ b/encodings/runend/src/ops.rs @@ -18,6 +18,8 @@ use crate::array::RunEndArrayExt; use crate::array::RunEndArraySlotsExt; impl OperationsVTable for RunEnd { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, RunEnd>, index: usize, diff --git a/encodings/sequence/src/array.rs b/encodings/sequence/src/array.rs index 10a222544b6..477da3191fe 100644 --- a/encodings/sequence/src/array.rs +++ b/encodings/sequence/src/array.rs @@ -426,6 +426,8 @@ impl VTable for Sequence { } impl OperationsVTable for Sequence { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Sequence>, index: usize, diff --git a/encodings/sparse/src/ops.rs b/encodings/sparse/src/ops.rs index 568d8d377d1..e5c45e47911 100644 --- a/encodings/sparse/src/ops.rs +++ b/encodings/sparse/src/ops.rs @@ -11,6 +11,8 @@ use crate::Sparse; use crate::SparseExt as _; impl OperationsVTable for Sparse { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Sparse>, index: usize, diff --git a/encodings/zigzag/src/array.rs b/encodings/zigzag/src/array.rs index ef3165132f4..4a7dc8b7436 100644 --- a/encodings/zigzag/src/array.rs +++ b/encodings/zigzag/src/array.rs @@ -231,6 +231,8 @@ impl Default for ZigZagData { } impl OperationsVTable for ZigZag { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, ZigZag>, index: usize, diff --git a/encodings/zstd/src/array.rs b/encodings/zstd/src/array.rs index fb3551e539b..66d0940de1e 100644 --- a/encodings/zstd/src/array.rs +++ b/encodings/zstd/src/array.rs @@ -1588,6 +1588,8 @@ impl ValidityVTable for Zstd { } impl OperationsVTable for Zstd { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Zstd>, index: usize, diff --git a/encodings/zstd/src/zstd_buffers.rs b/encodings/zstd/src/zstd_buffers.rs index f21deee6f64..912af56dd71 100644 --- a/encodings/zstd/src/zstd_buffers.rs +++ b/encodings/zstd/src/zstd_buffers.rs @@ -520,6 +520,8 @@ impl VTable for ZstdBuffers { } impl OperationsVTable for ZstdBuffers { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, ZstdBuffers>, index: usize, diff --git a/vortex-array/PROBE_DESIGN.md b/vortex-array/PROBE_DESIGN.md new file mode 100644 index 00000000000..f3cad7a5a08 --- /dev/null +++ b/vortex-array/PROBE_DESIGN.md @@ -0,0 +1,130 @@ +# Short-lived scalar probes + +Repeated scalar reads can redo validity evaluation, child dispatch, and decompression. +`ArrayProbe` gives an encoding a place to retain work for a short series of lookups, +without requiring callers to understand the encoding or execute the entire array. + +## Caller API + +```rust +let mut probe = array.probe(ProbeUsage::Repeated); +let first = probe.scalar_at(first_index, &mut ctx)?; +let second = probe.scalar_at(second_index, &mut ctx)?; + +let single = array.probe(ProbeUsage::Once).scalar_at(index, &mut ctx)?; +``` + +The probe borrows an `ArrayRef`. Construction allocates nothing and does not execute +anything. Scalar results own their values, include nullness, and can outlive the probe. +Bounds are checked before entering the encoding hook. A separate validity probe is not +needed: each encoding can resolve validity together with the value. + +`Repeated` permits preparation and reuse. `Once` requests no retained state; it is a +caching hint, so calling a one-off probe multiple times is still valid. Dropping the probe +releases its local state, child probes, and decoding resources. Probes are thread-local. + +## Vtable API + +The hook lives on `OperationsVTable`, selected by the array's existing `VTable`: + +```rust +type ProbeState<'a>: Default + 'a; + +fn probe_scalar<'a>( + array: ArrayView<'a, V>, + index: usize, + probe: Option<&mut ProbeCtx<'a, Self::ProbeState<'a>>>, + ctx: &mut ExecutionCtx, +) -> VortexResult { + array.array().execute_scalar(index, ctx) +} +``` + +All encodings initially select `ProbeState<'a> = ()` and use this default, including +primitive, PCO, FastLanes RLE, RunEnd, ScalarFn, and Zstd. The API change provides no +encoding-specific speedup on its own; optimized hooks are a separate follow-up. + +An encoding opting into preparation chooses its own concrete `ProbeState`. `Default` +should be cheap and avoid allocation or execution. Fallible preparation belongs in the +hook. The framework initializes one context on the first in-bounds repeated lookup and +passes that same context on subsequent calls. `Once` passes `None` without constructing +local state or child storage. The hook must preserve the logical dtype and handle nulls; +its contract differs from the legacy non-null `scalar_at` hook. + +## Local state and children + +```rust +pub struct ProbeCtx<'a, S> { + state: S, + children: ProbeChildren<'a>, +} +``` + +Encodings access local state with `probe.state_mut()` and request a retained child probe +with `probe.child(slot)?`. The framework binds child lookup to the original source's +slots and creates each child probe on its first request. Repeated requests reuse it; +creating the child does not initialize its encoding context or execute it. + +When local state and children must be borrowed simultaneously, use: + +```rust +let (state, children) = probe.parts(); +``` + +These are disjoint mutable borrows of the context's fields. Encoding-specific state does +not need to store child probes manually. A routing encoding can keep unit local state and +request the needed slots; a decoding encoding can keep owned indexes and decoded buffers. + +Each child has its own context and lazy child cache, so the API supports arbitrary nesting +when every parent routes through child probes. The cache key is `(parent probe, slot)`. +Separate roots have independent caches, and two slots referencing the same array still +have independent child state. Default hooks keep their existing scalar execution path. + +The child table is a `Vec>>`. It starts empty and allocates cells +for the source's slots on the first valid child request. Unrequested cells remain `None`. +Missing or out-of-bounds slots return an error before allocating a table. A dense table +reserves space for all slots, so wide arrays should be considered when measuring setup +cost. The vector provides the indirection required for recursive storage. + +## Lifetimes and ownership + +`'a` is the borrow of the root array. Every child slot is reachable for that lifetime; +child probes borrow the original slots without cloning array handles. Local state may +borrow views into the same source tree and own anything it prepares itself. + +A state cannot build a new array and store a probe borrowing that new array inside itself: +that would be self-referential. Owned decoded buffers can instead be read directly. +`use<'a>` controls capture for opaque `impl Trait` returns and does not replace the +associated state's outlives bound. + +## Storage and dispatch + +The entire typed `ProbeCtx<'a, S>` lives in erased storage owned by the probe. The holder +provides 128 inline bytes aligned to 16 bytes and a heap fallback for larger or more aligned +contexts. The child-cache header counts toward that capacity. Child tables, indexes, and +decoded buffers may allocate separately when an encoding needs them. + +```text +ArrayProbe::scalar_at + -> existing DynArrayData::probe_scalar dispatch + -> ArrayData retrieves ProbeCtx<'a, V's associated state> + -> OperationsVTable::probe_scalar +``` + +The adapter initializes the context with a source-bound factory, called once. Retrieving +it later uses a pointer cast with a fixed concrete type rather than an `Any` lookup. The +holder records a destructor for that exact type and drops it once, freeing a spilled +allocation and recursively dropping child probes. No pointer into inline storage is kept +across moves. + +All raw storage operations are confined to the holder. Its safety requires every access +to use the same concrete context type, including lifetimes. The probe's fixed source and +that source's vtable enforce this invariant. An invariant lifetime marker prevents erased +borrowed state from outliving the source. The holder is neither `Send` nor `Sync`, since +encoding state is not required to implement those traits. + +Tests cover inline and spilled storage, alignment, moves, borrowed state, exactly-once +initialization and destruction, default scalar behavior, bounds and nulls, lazy child +creation, repeated slot reuse, source binding, independent slots/contexts, and simultaneous +local-state and child access. Encoding follow-ups should test recursive cache reuse and +benchmark complete probe lifetimes, including preparation and teardown. diff --git a/vortex-array/src/array/mod.rs b/vortex-array/src/array/mod.rs index 8b9b2812bfa..2431a6a93cb 100644 --- a/vortex-array/src/array/mod.rs +++ b/vortex-array/src/array/mod.rs @@ -31,6 +31,13 @@ pub use erased::*; mod plugin; pub use plugin::*; +mod probe; +pub use probe::ArrayProbe; +pub use probe::ProbeChildren; +pub use probe::ProbeCtx; +use probe::ProbeStorage; +pub use probe::ProbeUsage; + mod foreign; pub(crate) use foreign::*; @@ -229,6 +236,14 @@ pub(crate) trait DynArrayData: 'static + private::Sealed + Send + Sync + Debug { index: usize, ctx: &mut ExecutionCtx, ) -> VortexResult; + + fn probe_scalar<'a>( + &'a self, + this: &'a ArrayRef, + index: usize, + state: Option<&mut ProbeStorage<'a>>, + ctx: &mut ExecutionCtx, + ) -> VortexResult; } /// Trait for converting a type into a Vortex [`ArrayRef`]. @@ -499,6 +514,29 @@ impl DynArrayData for ArrayData { let view = unsafe { ArrayView::new_unchecked(this, &self.data) }; >::scalar_at(view, index, ctx) } + + fn probe_scalar<'a>( + &'a self, + this: &'a ArrayRef, + index: usize, + state: Option<&mut ProbeStorage<'a>>, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + // SAFETY: this adapter belongs to the ArrayData stored in `this`. + let view = unsafe { ArrayView::new_unchecked(this, &self.data) }; + let state = state.map(|storage| { + // SAFETY: ArrayProbe fixes its source for its entire lifetime, so this storage + // is accessed only through this adapter with the same associated context type. + unsafe { + storage.get_or_init(|| { + ProbeCtx::<>::ProbeState<'a>>::new( + this, + ) + }) + } + }); + >::probe_scalar(view, index, state, ctx) + } } /// Wrapper around `&mut dyn Hasher` that implements `Hasher` (and is `Sized`). diff --git a/vortex-array/src/array/probe.rs b/vortex-array/src/array/probe.rs new file mode 100644 index 00000000000..0219460d96b --- /dev/null +++ b/vortex-array/src/array/probe.rs @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Random scalar access with optional, encoding-specific retained state. + +use std::marker::PhantomData; +use std::mem::MaybeUninit; +use std::ptr::NonNull; + +use vortex_error::VortexResult; +use vortex_error::vortex_ensure; +use vortex_error::vortex_err; + +use crate::ArrayRef; +use crate::ExecutionCtx; +use crate::scalar::Scalar; + +/// Whether scalar access should retain preparation for subsequent lookups. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ProbeUsage { + /// Use temporary resources only, without initializing retained probe state. + Once, + /// Allow the encoding to retain preparation and decoded data between lookups. + Repeated, +} + +/// A borrowed scalar accessor that retains encoding-specific state until it is dropped. +/// +/// `'a` is the borrow of the root array. Its [`ProbeCtx`] retains a child probe for each +/// requested slot, so one lifetime covers the whole tree and no array handles are cloned. +/// Anything an encoding builds itself, such as a decoded page or validity mask, is owned +/// by its state. +/// +/// Construction never allocates or executes the array. Repeated access initializes the +/// encoding's context on the first in-bounds lookup. Small contexts live inline; larger or +/// more aligned contexts use a heap allocation. Child storage and decoding may allocate +/// separately, when first needed. +/// +/// `Once` is a caching hint, not a restriction on how many times the probe may be called. +/// Returned scalars own their values and can outlive the probe. Probes are local to a thread. +pub struct ArrayProbe<'a> { + array: &'a ArrayRef, + state: Option>, +} + +/// Local encoding state and lazy child probes retained for one source array. +/// +/// The framework initializes this context once for repeated access and passes it to +/// [`OperationsVTable::probe_scalar`](crate::vtable::OperationsVTable::probe_scalar). +/// One-off access receives `None` instead. `S` is the encoding's associated state type; +/// it may borrow the source tree for `'a` and own any prepared resources. +pub struct ProbeCtx<'a, S> { + state: S, + children: ProbeChildren<'a>, +} + +impl<'a, S: Default> ProbeCtx<'a, S> { + pub(crate) fn new(array: &'a ArrayRef) -> Self { + Self { + state: S::default(), + children: ProbeChildren { + array, + slots: Vec::new(), + }, + } + } +} + +impl<'a, S> ProbeCtx<'a, S> { + /// Access the encoding's retained local state. + pub fn state_mut(&mut self) -> &mut S { + &mut self.state + } + + /// Get the retained probe for a source slot, creating it on the first request. + /// + /// Returns an error for an absent or out-of-bounds slot. Creating a child probe does + /// not execute the child or initialize its encoding state. + pub fn child(&mut self, slot: usize) -> VortexResult<&mut ArrayProbe<'a>> { + self.children.child(slot) + } + + /// Borrow local state and child access together, allowing disjoint mutable access. + pub fn parts(&mut self) -> (&mut S, &mut ProbeChildren<'a>) { + (&mut self.state, &mut self.children) + } +} + +/// Lazy child probes bound to the slots of one source array. +/// +/// Obtain this through [`ProbeCtx::parts`] when retaining a mutable borrow of local state +/// while accessing children. Each slot has independent state, even if two slots reference +/// the same array. The slot table allocates on its first valid request; unrequested slots +/// remain empty. Dropping the parent context drops every created child probe. +pub struct ProbeChildren<'a> { + array: &'a ArrayRef, + slots: Vec>>, +} + +impl<'a> ProbeChildren<'a> { + /// Get or create a repeated-access probe for the given source slot. + /// + /// Returns an error for an absent or out-of-bounds slot without allocating a slot table. + pub fn child(&mut self, slot: usize) -> VortexResult<&mut ArrayProbe<'a>> { + let child = self + .array + .slots() + .get(slot) + .and_then(Option::as_ref) + .ok_or_else(|| vortex_err!("Probe child slot {slot} is missing"))?; + if self.slots.is_empty() { + self.slots.resize_with(self.array.slots().len(), || None); + } + Ok(self.slots[slot].get_or_insert_with(|| child.probe(ProbeUsage::Repeated))) + } +} + +impl ArrayRef { + /// Create an accessor with the requested policy for retaining state between scalar lookups. + /// + /// ``` + /// use vortex_array::{IntoArray, ProbeUsage, VortexSessionExecute}; + /// use vortex_array::arrays::PrimitiveArray; + /// + /// let array = PrimitiveArray::from_iter([10i32, 20, 30]).into_array(); + /// let mut ctx = vortex_array::array_session().create_execution_ctx(); + /// let mut probe = array.probe(ProbeUsage::Repeated); + /// assert_eq!(probe.scalar_at(2, &mut ctx)?, 30i32.into()); + /// assert_eq!(probe.scalar_at(0, &mut ctx)?, 10i32.into()); + /// # Ok::<(), vortex_error::VortexError>(()) + /// ``` + pub fn probe(&self, usage: ProbeUsage) -> ArrayProbe<'_> { + ArrayProbe { + array: self, + state: match usage { + ProbeUsage::Once => None, + ProbeUsage::Repeated => Some(ProbeStorage::new()), + }, + } + } +} + +impl<'a> ArrayProbe<'a> { + /// The array this probe reads from. + pub fn array(&self) -> &'a ArrayRef { + self.array + } + + /// Read a scalar, including its nullness, preparing and reusing state as appropriate. + pub fn scalar_at(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { + vortex_ensure!(index < self.array.len(), OutOfBounds: index, 0, self.array.len()); + let scalar = + self.array + .dyn_array() + .probe_scalar(self.array, index, self.state.as_mut(), ctx)?; + debug_assert_eq!(scalar.dtype(), self.array.dtype(), "Scalar dtype mismatch"); + Ok(scalar) + } +} + +// An open set of external state types cannot be represented by a fixed Rust enum. Erasing +// them inline avoids a mandatory Box allocation; all raw storage operations are confined here. +#[repr(align(16))] +struct InlineStorage(MaybeUninit<[u8; 128]>); + +pub(crate) struct ProbeStorage<'a> { + inline: InlineStorage, + heap: Option>, + drop_fn: Option, + // The erased state may borrow the root array tree, and the type system cannot see that + // borrow once erased. This marker ties the storage to `'a` (invariantly) so it cannot + // outlive the borrow. NonNull also keeps the erased storage !Send and !Sync, since we + // impose neither bound on state types. + _lifetime: PhantomData<&'a mut &'a ()>, +} + +impl<'a> ProbeStorage<'a> { + fn new() -> Self { + Self { + inline: InlineStorage(MaybeUninit::uninit()), + heap: None, + drop_fn: None, + _lifetime: PhantomData, + } + } + + /// # Safety + /// + /// Every call on this storage must use the same `T`, including its lifetime parameters. + /// The owning ArrayProbe fixes its source array, and only that array's erased adapter + /// may initialize/access this storage using its associated ProbeCtx type. + pub(crate) unsafe fn get_or_init(&mut self, init: impl FnOnce() -> T) -> &mut T { + if self.drop_fn.is_none() { + let value = init(); + if size_of::() <= size_of::() + && align_of::() <= align_of::() + { + // SAFETY: the size/alignment checks make the inline region suitable for T, + // and no value has been initialized in this storage yet. + unsafe { self.inline.0.as_mut_ptr().cast::().write(value) }; + self.drop_fn = Some(drop_inline::); + } else { + self.heap = Some(NonNull::from(Box::leak(Box::new(value))).cast()); + self.drop_fn = Some(drop_heap::); + } + } + // SAFETY: initialization above, or the caller's same-T invariant, establishes a + // live T at this pointer. The exclusive storage borrow provides exclusive access. + unsafe { &mut *self.as_mut_ptr().cast::() } + } + + fn as_mut_ptr(&mut self) -> *mut u8 { + match self.heap { + Some(ptr) => ptr.as_ptr(), + None => self.inline.0.as_mut_ptr().cast(), + } + } +} + +impl Drop for ProbeStorage<'_> { + fn drop(&mut self) { + if let Some(drop_fn) = self.drop_fn { + // SAFETY: the drop shim was installed only after its matching T was initialized. + // No pointer into inline storage is retained across moves of ProbeStorage. + unsafe { drop_fn(self.as_mut_ptr()) }; + } + } +} + +unsafe fn drop_inline(ptr: *mut u8) { + // SAFETY: the storage installed this shim for an initialized inline T. + unsafe { ptr.cast::().drop_in_place() }; +} + +unsafe fn drop_heap(ptr: *mut u8) { + // SAFETY: the storage installed this shim for a T allocated by Box::new and leaked once. + drop(unsafe { Box::from_raw(ptr.cast::()) }); +} + +#[cfg(test)] +mod tests; diff --git a/vortex-array/src/array/probe/tests.rs b/vortex-array/src/array/probe/tests.rs new file mode 100644 index 00000000000..6310e289fb3 --- /dev/null +++ b/vortex-array/src/array/probe/tests.rs @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use std::cell::Cell; +use std::rc::Rc; + +use rstest::rstest; +use vortex_error::VortexResult; + +use super::ProbeCtx; +use super::ProbeStorage; +use super::ProbeUsage; +use crate::IntoArray; +use crate::VortexSessionExecute; +use crate::arrays::PrimitiveArray; +use crate::arrays::StructArray; + +#[derive(Default)] +struct Tracked { + drops: Rc>, +} + +impl Drop for Tracked { + fn drop(&mut self) { + self.drops.set(self.drops.get() + 1); + } +} + +#[derive(Default)] +struct Large { + tracked: Tracked, + padding: [[u8; 32]; 8], +} + +#[repr(align(64))] +#[derive(Default)] +struct Aligned(Tracked); + +#[derive(Default)] +struct Borrowed<'a>(Option<&'a usize>); + +#[test] +fn inline_state_survives_moves_and_drops_once() { + let mut storage = ProbeStorage::new(); + let initialized = Cell::new(0); + // SAFETY: every access to this storage uses Tracked. + let drops = Rc::clone( + &unsafe { + storage.get_or_init(|| { + initialized.set(initialized.get() + 1); + Tracked::default() + }) + } + .drops, + ); + assert!(storage.heap.is_none()); + let mut moved = (storage, 1); + // SAFETY: the moved storage still contains Tracked. + assert!(Rc::ptr_eq( + &unsafe { + moved.0.get_or_init(|| { + initialized.set(initialized.get() + 1); + Tracked::default() + }) + } + .drops, + &drops + )); + drop(moved); + assert_eq!(initialized.get(), 1); + assert_eq!(drops.get(), 1); +} + +#[test] +fn children_are_lazy_reused_and_bound_to_their_source_slots() -> VortexResult<()> { + let leaf = PrimitiveArray::from_iter([10i32, 20]).into_array(); + let array = StructArray::from_fields(&[("left", leaf.clone()), ("right", leaf)])?.into_array(); + let mut ctx = crate::array_session().create_execution_ctx(); + let mut probe = ProbeCtx::::new(&array); + assert_eq!(probe.children.slots.capacity(), 0); + // Slot zero is the absent struct validity; invalid requests must not allocate. + assert!(probe.child(0).is_err()); + assert!(probe.child(99).is_err()); + assert_eq!(probe.children.slots.capacity(), 0); + + let first = std::ptr::from_mut(probe.child(1)?); + assert!(probe.children.slots[2].is_none()); + let (state, children) = probe.parts(); + let child = children.child(1)?; + assert!( + child + .state + .as_ref() + .is_some_and(|state| state.drop_fn.is_none()) + ); + assert!(child.scalar_at(2, &mut ctx).is_err()); + assert!( + child + .state + .as_ref() + .is_some_and(|state| state.drop_fn.is_none()) + ); + assert_eq!(child.scalar_at(0, &mut ctx)?, 10i32.into()); + *state += 1; + assert_eq!(*probe.state_mut(), 1); + assert_eq!(std::ptr::from_mut(probe.child(1)?), first); + assert!( + probe + .child(1)? + .state + .as_ref() + .is_some_and(|state| state.drop_fn.is_some()) + ); + + // Identical sources in different slots still get independent probe state. + assert_ne!(std::ptr::from_mut(probe.child(2)?), first); + let source = array.slots()[1] + .as_ref() + .ok_or_else(|| vortex_error::vortex_err!("missing fixture slot"))?; + assert!(std::ptr::eq(probe.child(1)?.array(), source)); + assert!( + probe + .child(2)? + .state + .as_ref() + .is_some_and(|state| state.drop_fn.is_none()) + ); + let mut other = ProbeCtx::::new(&array); + assert!( + other + .child(1)? + .state + .as_ref() + .is_some_and(|state| state.drop_fn.is_none()) + ); + + let mut moved = (probe, ()); + assert_eq!(std::ptr::from_mut(moved.0.child(1)?), first); + assert_eq!(moved.0.child(1)?.scalar_at(1, &mut ctx)?, 20i32.into()); + Ok(()) +} + +#[test] +fn oversized_state_spills_and_drops_once() { + let mut storage = ProbeStorage::new(); + // SAFETY: every access to this storage uses Large. + let state = unsafe { storage.get_or_init::(Large::default) }; + state.padding[7][31] = 42; + let drops = Rc::clone(&state.tracked.drops); + assert!(storage.heap.is_some()); + let mut moved = (storage, 1); + // SAFETY: the moved storage still contains Large. + assert_eq!( + unsafe { moved.0.get_or_init::(Large::default) }.padding[7][31], + 42 + ); + drop(moved); + assert_eq!(drops.get(), 1); +} + +#[test] +fn over_aligned_state_spills() { + let mut storage = ProbeStorage::new(); + // SAFETY: this storage is only used for Aligned. + let state = unsafe { storage.get_or_init::(Aligned::default) }; + assert_eq!(std::ptr::from_ref(state).addr() % 64, 0); + let drops = Rc::clone(&state.0.drops); + assert!(storage.heap.is_some()); + drop(storage); + assert_eq!(drops.get(), 1); +} + +#[test] +fn state_can_borrow_and_unit_needs_no_allocation() { + let value = 42; + let mut borrowed = ProbeStorage::new(); + // SAFETY: the storage always contains Borrowed with the same source lifetime. + unsafe { borrowed.get_or_init::>(Borrowed::default) }.0 = Some(&value); + // SAFETY: same type and source lifetime as initialization. + assert_eq!( + unsafe { borrowed.get_or_init::>(Borrowed::default) }.0, + Some(&42) + ); + let mut unit = ProbeStorage::new(); + // SAFETY: this separate storage is only used for (). + unsafe { unit.get_or_init::<()>(Default::default) }; + assert!(unit.heap.is_none()); +} + +#[rstest] +#[case(ProbeUsage::Once)] +#[case(ProbeUsage::Repeated)] +fn access_checks_bounds_and_nulls(#[case] usage: ProbeUsage) -> VortexResult<()> { + let array = PrimitiveArray::from_option_iter([Some(10i32), None, Some(30)]).into_array(); + let mut ctx = crate::array_session().create_execution_ctx(); + let mut probe = array.probe(usage); + assert!( + probe + .state + .as_ref() + .is_none_or(|state| state.drop_fn.is_none()) + ); + assert!(probe.scalar_at(3, &mut ctx).is_err()); + assert!( + probe + .state + .as_ref() + .is_none_or(|state| state.drop_fn.is_none()) + ); + for index in [2, 1, 0, 2] { + assert_eq!( + probe.scalar_at(index, &mut ctx)?, + array.execute_scalar(index, &mut ctx)? + ); + } + if usage == ProbeUsage::Once { + assert!(probe.state.is_none()); + } + Ok(()) +} diff --git a/vortex-array/src/array/vtable/operations.rs b/vortex-array/src/array/vtable/operations.rs index 7f49e683640..883c70a1b47 100644 --- a/vortex-array/src/array/vtable/operations.rs +++ b/vortex-array/src/array/vtable/operations.rs @@ -5,6 +5,7 @@ use vortex_error::VortexResult; use vortex_error::vortex_bail; use crate::ExecutionCtx; +use crate::ProbeCtx; use crate::array::ArrayView; use crate::array::VTable; use crate::scalar::Scalar; @@ -17,6 +18,30 @@ use crate::vtable::NotSupported; /// [`ArrayRef`](crate::ArrayRef) /// methods perform common checks before dispatching here. pub trait OperationsVTable { + /// Encoding-specific state retained by repeated scalar access. + /// + /// Default construction should be cheap and avoid allocation or execution. Preparation + /// belongs in [`Self::probe_scalar`]. `'a` is the borrow of the root array, so state may + /// retain views into its source tree. Request retained child probes through [`ProbeCtx`]. + /// Use `()` when no local state is needed. + type ProbeState<'a>: Default + 'a; + + /// Read a scalar, handling nullness and optionally retaining state for subsequent reads. + /// + /// Bounds have been checked, but the row may be null. `None` requests one-off access and + /// never initializes a context; `Some` reuses local state and child probes for this source. + /// The scalar must retain the source's logical dtype, including nullability. + /// + /// The default preserves the existing scalar path without adding caching. + fn probe_scalar<'a>( + array: ArrayView<'a, V>, + index: usize, + _probe: Option<&mut ProbeCtx<'a, Self::ProbeState<'a>>>, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + array.array().execute_scalar(index, ctx) + } + /// Fetch the scalar at the given index. /// /// ## Preconditions @@ -35,6 +60,8 @@ pub trait OperationsVTable { } impl OperationsVTable for NotSupported { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, V>, _index: usize, diff --git a/vortex-array/src/arrays/bool/vtable/operations.rs b/vortex-array/src/arrays/bool/vtable/operations.rs index c29ab20331b..5a91469eae5 100644 --- a/vortex-array/src/arrays/bool/vtable/operations.rs +++ b/vortex-array/src/arrays/bool/vtable/operations.rs @@ -11,6 +11,8 @@ use crate::arrays::bool::BoolArrayExt; use crate::scalar::Scalar; impl OperationsVTable for Bool { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Bool>, index: usize, diff --git a/vortex-array/src/arrays/chunked/vtable/operations.rs b/vortex-array/src/arrays/chunked/vtable/operations.rs index 8f9e0867a88..d5484a3a538 100644 --- a/vortex-array/src/arrays/chunked/vtable/operations.rs +++ b/vortex-array/src/arrays/chunked/vtable/operations.rs @@ -11,6 +11,8 @@ use crate::arrays::chunked::ChunkedArrayExt; use crate::scalar::Scalar; impl OperationsVTable for Chunked { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Chunked>, index: usize, diff --git a/vortex-array/src/arrays/constant/vtable/operations.rs b/vortex-array/src/arrays/constant/vtable/operations.rs index e3568a9c39f..d7ada7ee274 100644 --- a/vortex-array/src/arrays/constant/vtable/operations.rs +++ b/vortex-array/src/arrays/constant/vtable/operations.rs @@ -10,6 +10,8 @@ use crate::arrays::Constant; use crate::scalar::Scalar; impl OperationsVTable for Constant { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Constant>, _index: usize, diff --git a/vortex-array/src/arrays/decimal/vtable/operations.rs b/vortex-array/src/arrays/decimal/vtable/operations.rs index 257f26127ae..2eae2715b73 100644 --- a/vortex-array/src/arrays/decimal/vtable/operations.rs +++ b/vortex-array/src/arrays/decimal/vtable/operations.rs @@ -12,6 +12,8 @@ use crate::scalar::DecimalValue; use crate::scalar::Scalar; impl OperationsVTable for Decimal { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Decimal>, index: usize, diff --git a/vortex-array/src/arrays/dict/vtable/operations.rs b/vortex-array/src/arrays/dict/vtable/operations.rs index 1982a1e0870..a127c2febad 100644 --- a/vortex-array/src/arrays/dict/vtable/operations.rs +++ b/vortex-array/src/arrays/dict/vtable/operations.rs @@ -12,6 +12,8 @@ use crate::arrays::dict::DictArraySlotsExt; use crate::scalar::Scalar; impl OperationsVTable for Dict { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Dict>, index: usize, diff --git a/vortex-array/src/arrays/extension/vtable/operations.rs b/vortex-array/src/arrays/extension/vtable/operations.rs index 66de94b596a..b6843612955 100644 --- a/vortex-array/src/arrays/extension/vtable/operations.rs +++ b/vortex-array/src/arrays/extension/vtable/operations.rs @@ -11,6 +11,8 @@ use crate::arrays::extension::ExtensionArrayExt; use crate::scalar::Scalar; impl OperationsVTable for Extension { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Extension>, index: usize, diff --git a/vortex-array/src/arrays/filter/vtable.rs b/vortex-array/src/arrays/filter/vtable.rs index 56fc112aa16..8301b682fd9 100644 --- a/vortex-array/src/arrays/filter/vtable.rs +++ b/vortex-array/src/arrays/filter/vtable.rs @@ -200,6 +200,8 @@ impl VTable for Filter { } } impl OperationsVTable for Filter { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Filter>, index: usize, diff --git a/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs b/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs index 9f4cf02fbf8..e31892e0215 100644 --- a/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs +++ b/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs @@ -11,6 +11,8 @@ use crate::arrays::fixed_size_list::FixedSizeListArrayExt; use crate::scalar::Scalar; impl OperationsVTable for FixedSizeList { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, FixedSizeList>, index: usize, diff --git a/vortex-array/src/arrays/interleave/mod.rs b/vortex-array/src/arrays/interleave/mod.rs index d29981249d4..94fa0d77796 100644 --- a/vortex-array/src/arrays/interleave/mod.rs +++ b/vortex-array/src/arrays/interleave/mod.rs @@ -389,6 +389,8 @@ impl VTable for Interleave { } impl OperationsVTable for Interleave { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Interleave>, index: usize, diff --git a/vortex-array/src/arrays/list/vtable/operations.rs b/vortex-array/src/arrays/list/vtable/operations.rs index 02c686cd1f1..688e635c3b0 100644 --- a/vortex-array/src/arrays/list/vtable/operations.rs +++ b/vortex-array/src/arrays/list/vtable/operations.rs @@ -13,6 +13,8 @@ use crate::arrays::list::ListArrayExt; use crate::scalar::Scalar; impl OperationsVTable for List { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, List>, index: usize, diff --git a/vortex-array/src/arrays/listview/vtable/operations.rs b/vortex-array/src/arrays/listview/vtable/operations.rs index f0cb9539cc3..ec83518eae7 100644 --- a/vortex-array/src/arrays/listview/vtable/operations.rs +++ b/vortex-array/src/arrays/listview/vtable/operations.rs @@ -13,6 +13,8 @@ use crate::arrays::listview::ListViewArrayExt; use crate::scalar::Scalar; impl OperationsVTable for ListView { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, ListView>, index: usize, diff --git a/vortex-array/src/arrays/map/vtable/operations.rs b/vortex-array/src/arrays/map/vtable/operations.rs index d6e8fe87f12..bb7be8274d0 100644 --- a/vortex-array/src/arrays/map/vtable/operations.rs +++ b/vortex-array/src/arrays/map/vtable/operations.rs @@ -13,6 +13,8 @@ use crate::arrays::struct_::StructArrayExt; use crate::scalar::Scalar; impl OperationsVTable for Map { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Map>, index: usize, diff --git a/vortex-array/src/arrays/masked/vtable/operations.rs b/vortex-array/src/arrays/masked/vtable/operations.rs index c82d0bf03ed..673738520b2 100644 --- a/vortex-array/src/arrays/masked/vtable/operations.rs +++ b/vortex-array/src/arrays/masked/vtable/operations.rs @@ -11,6 +11,8 @@ use crate::arrays::masked::MaskedArraySlotsExt; use crate::scalar::Scalar; impl OperationsVTable for Masked { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Masked>, index: usize, diff --git a/vortex-array/src/arrays/null/mod.rs b/vortex-array/src/arrays/null/mod.rs index 8479b7137cc..057188a2e16 100644 --- a/vortex-array/src/arrays/null/mod.rs +++ b/vortex-array/src/arrays/null/mod.rs @@ -175,6 +175,8 @@ impl Array { } impl OperationsVTable for Null { + type ProbeState<'a> = (); + fn scalar_at( _array: ArrayView<'_, Null>, _index: usize, diff --git a/vortex-array/src/arrays/patched/vtable/operations.rs b/vortex-array/src/arrays/patched/vtable/operations.rs index 51dd1fc9e3c..2135943164e 100644 --- a/vortex-array/src/arrays/patched/vtable/operations.rs +++ b/vortex-array/src/arrays/patched/vtable/operations.rs @@ -14,6 +14,8 @@ use crate::optimizer::ArrayOptimizer; use crate::scalar::Scalar; impl OperationsVTable for Patched { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Patched>, index: usize, diff --git a/vortex-array/src/arrays/piecewise_sequence/vtable.rs b/vortex-array/src/arrays/piecewise_sequence/vtable.rs index d68a00815f3..a40d8f1e8a1 100644 --- a/vortex-array/src/arrays/piecewise_sequence/vtable.rs +++ b/vortex-array/src/arrays/piecewise_sequence/vtable.rs @@ -145,6 +145,8 @@ impl VTable for PiecewiseSequence { } impl OperationsVTable for PiecewiseSequence { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, PiecewiseSequence>, index: usize, diff --git a/vortex-array/src/arrays/primitive/vtable/operations.rs b/vortex-array/src/arrays/primitive/vtable/operations.rs index ddeaa386485..7cc7e5a47a6 100644 --- a/vortex-array/src/arrays/primitive/vtable/operations.rs +++ b/vortex-array/src/arrays/primitive/vtable/operations.rs @@ -11,6 +11,8 @@ use crate::match_each_native_ptype; use crate::scalar::Scalar; impl OperationsVTable for Primitive { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Primitive>, index: usize, diff --git a/vortex-array/src/arrays/scalar_fn/vtable/operations.rs b/vortex-array/src/arrays/scalar_fn/vtable/operations.rs index 40d75906356..75a2875cb82 100644 --- a/vortex-array/src/arrays/scalar_fn/vtable/operations.rs +++ b/vortex-array/src/arrays/scalar_fn/vtable/operations.rs @@ -15,6 +15,8 @@ use crate::scalar::Scalar; use crate::scalar_fn::VecExecutionArgs; impl OperationsVTable for ScalarFn { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, ScalarFn>, index: usize, diff --git a/vortex-array/src/arrays/shared/vtable.rs b/vortex-array/src/arrays/shared/vtable.rs index 758d091b557..9d097d7914f 100644 --- a/vortex-array/src/arrays/shared/vtable.rs +++ b/vortex-array/src/arrays/shared/vtable.rs @@ -125,6 +125,8 @@ impl VTable for Shared { } } impl OperationsVTable for Shared { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Shared>, index: usize, diff --git a/vortex-array/src/arrays/slice/vtable.rs b/vortex-array/src/arrays/slice/vtable.rs index c88c28a9a4d..8aa7d65f21e 100644 --- a/vortex-array/src/arrays/slice/vtable.rs +++ b/vortex-array/src/arrays/slice/vtable.rs @@ -169,6 +169,8 @@ impl VTable for Slice { } } impl OperationsVTable for Slice { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Slice>, index: usize, diff --git a/vortex-array/src/arrays/struct_/vtable/operations.rs b/vortex-array/src/arrays/struct_/vtable/operations.rs index b491e231520..35b70fbeef5 100644 --- a/vortex-array/src/arrays/struct_/vtable/operations.rs +++ b/vortex-array/src/arrays/struct_/vtable/operations.rs @@ -12,6 +12,8 @@ use crate::scalar::Scalar; use crate::scalar::ScalarValue; impl OperationsVTable for Struct { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Struct>, index: usize, diff --git a/vortex-array/src/arrays/union/vtable/operations.rs b/vortex-array/src/arrays/union/vtable/operations.rs index 39ba95ed4ee..2da1e623f90 100644 --- a/vortex-array/src/arrays/union/vtable/operations.rs +++ b/vortex-array/src/arrays/union/vtable/operations.rs @@ -14,6 +14,8 @@ use crate::arrays::union::UnionArraySlotsExt; use crate::scalar::Scalar; impl OperationsVTable for Union { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Union>, index: usize, diff --git a/vortex-array/src/arrays/varbin/vtable/operations.rs b/vortex-array/src/arrays/varbin/vtable/operations.rs index e11043e605a..4f2ada9c948 100644 --- a/vortex-array/src/arrays/varbin/vtable/operations.rs +++ b/vortex-array/src/arrays/varbin/vtable/operations.rs @@ -12,6 +12,8 @@ use crate::arrays::varbin::varbin_scalar; use crate::scalar::Scalar; impl OperationsVTable for VarBin { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, VarBin>, index: usize, diff --git a/vortex-array/src/arrays/varbinview/vtable/operations.rs b/vortex-array/src/arrays/varbinview/vtable/operations.rs index 1a1f20a0dbe..1e48be20c9f 100644 --- a/vortex-array/src/arrays/varbinview/vtable/operations.rs +++ b/vortex-array/src/arrays/varbinview/vtable/operations.rs @@ -11,6 +11,8 @@ use crate::arrays::varbin::varbin_scalar; use crate::scalar::Scalar; impl OperationsVTable for VarBinView { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, VarBinView>, index: usize, diff --git a/vortex-array/src/arrays/variant/vtable/operations.rs b/vortex-array/src/arrays/variant/vtable/operations.rs index c7f7d36fd97..eabf6ba842b 100644 --- a/vortex-array/src/arrays/variant/vtable/operations.rs +++ b/vortex-array/src/arrays/variant/vtable/operations.rs @@ -12,6 +12,8 @@ use crate::arrays::variant::VariantArraySlotsExt; use crate::scalar::Scalar; impl OperationsVTable for Variant { + type ProbeState<'a> = (); + fn scalar_at( array: ArrayView<'_, Variant>, index: usize, diff --git a/vortex-python/src/arrays/py/vtable.rs b/vortex-python/src/arrays/py/vtable.rs index 5ec749d3a5f..edb9156ccf0 100644 --- a/vortex-python/src/arrays/py/vtable.rs +++ b/vortex-python/src/arrays/py/vtable.rs @@ -122,6 +122,8 @@ impl VTable for PythonVTable { } impl OperationsVTable for PythonVTable { + type ProbeState<'a> = (); + fn scalar_at( _array: ArrayView<'_, PythonVTable>, _index: usize, From 9ca43c41e67e0e7df4953c42a0a92c5fb42ad7b5 Mon Sep 17 00:00:00 2001 From: Joe Isaacs Date: Mon, 14 Sep 2026 15:03:54 +0100 Subject: [PATCH 2/9] refactor(array): simplify probe access and storage Replace inline type-erased storage with a lazy boxed implementation and leave a FIXME to revisit inline storage only if benchmarks justify it. Signed-off-by: Joe Isaacs --- vortex-array/PROBE_DESIGN.md | 130 -------------- vortex-array/src/array/mod.rs | 25 ++- vortex-array/src/array/probe.rs | 189 +++++++++++-------- vortex-array/src/array/probe/tests.rs | 190 ++++++++++---------- vortex-array/src/array/vtable/operations.rs | 10 +- 5 files changed, 217 insertions(+), 327 deletions(-) delete mode 100644 vortex-array/PROBE_DESIGN.md diff --git a/vortex-array/PROBE_DESIGN.md b/vortex-array/PROBE_DESIGN.md deleted file mode 100644 index f3cad7a5a08..00000000000 --- a/vortex-array/PROBE_DESIGN.md +++ /dev/null @@ -1,130 +0,0 @@ -# Short-lived scalar probes - -Repeated scalar reads can redo validity evaluation, child dispatch, and decompression. -`ArrayProbe` gives an encoding a place to retain work for a short series of lookups, -without requiring callers to understand the encoding or execute the entire array. - -## Caller API - -```rust -let mut probe = array.probe(ProbeUsage::Repeated); -let first = probe.scalar_at(first_index, &mut ctx)?; -let second = probe.scalar_at(second_index, &mut ctx)?; - -let single = array.probe(ProbeUsage::Once).scalar_at(index, &mut ctx)?; -``` - -The probe borrows an `ArrayRef`. Construction allocates nothing and does not execute -anything. Scalar results own their values, include nullness, and can outlive the probe. -Bounds are checked before entering the encoding hook. A separate validity probe is not -needed: each encoding can resolve validity together with the value. - -`Repeated` permits preparation and reuse. `Once` requests no retained state; it is a -caching hint, so calling a one-off probe multiple times is still valid. Dropping the probe -releases its local state, child probes, and decoding resources. Probes are thread-local. - -## Vtable API - -The hook lives on `OperationsVTable`, selected by the array's existing `VTable`: - -```rust -type ProbeState<'a>: Default + 'a; - -fn probe_scalar<'a>( - array: ArrayView<'a, V>, - index: usize, - probe: Option<&mut ProbeCtx<'a, Self::ProbeState<'a>>>, - ctx: &mut ExecutionCtx, -) -> VortexResult { - array.array().execute_scalar(index, ctx) -} -``` - -All encodings initially select `ProbeState<'a> = ()` and use this default, including -primitive, PCO, FastLanes RLE, RunEnd, ScalarFn, and Zstd. The API change provides no -encoding-specific speedup on its own; optimized hooks are a separate follow-up. - -An encoding opting into preparation chooses its own concrete `ProbeState`. `Default` -should be cheap and avoid allocation or execution. Fallible preparation belongs in the -hook. The framework initializes one context on the first in-bounds repeated lookup and -passes that same context on subsequent calls. `Once` passes `None` without constructing -local state or child storage. The hook must preserve the logical dtype and handle nulls; -its contract differs from the legacy non-null `scalar_at` hook. - -## Local state and children - -```rust -pub struct ProbeCtx<'a, S> { - state: S, - children: ProbeChildren<'a>, -} -``` - -Encodings access local state with `probe.state_mut()` and request a retained child probe -with `probe.child(slot)?`. The framework binds child lookup to the original source's -slots and creates each child probe on its first request. Repeated requests reuse it; -creating the child does not initialize its encoding context or execute it. - -When local state and children must be borrowed simultaneously, use: - -```rust -let (state, children) = probe.parts(); -``` - -These are disjoint mutable borrows of the context's fields. Encoding-specific state does -not need to store child probes manually. A routing encoding can keep unit local state and -request the needed slots; a decoding encoding can keep owned indexes and decoded buffers. - -Each child has its own context and lazy child cache, so the API supports arbitrary nesting -when every parent routes through child probes. The cache key is `(parent probe, slot)`. -Separate roots have independent caches, and two slots referencing the same array still -have independent child state. Default hooks keep their existing scalar execution path. - -The child table is a `Vec>>`. It starts empty and allocates cells -for the source's slots on the first valid child request. Unrequested cells remain `None`. -Missing or out-of-bounds slots return an error before allocating a table. A dense table -reserves space for all slots, so wide arrays should be considered when measuring setup -cost. The vector provides the indirection required for recursive storage. - -## Lifetimes and ownership - -`'a` is the borrow of the root array. Every child slot is reachable for that lifetime; -child probes borrow the original slots without cloning array handles. Local state may -borrow views into the same source tree and own anything it prepares itself. - -A state cannot build a new array and store a probe borrowing that new array inside itself: -that would be self-referential. Owned decoded buffers can instead be read directly. -`use<'a>` controls capture for opaque `impl Trait` returns and does not replace the -associated state's outlives bound. - -## Storage and dispatch - -The entire typed `ProbeCtx<'a, S>` lives in erased storage owned by the probe. The holder -provides 128 inline bytes aligned to 16 bytes and a heap fallback for larger or more aligned -contexts. The child-cache header counts toward that capacity. Child tables, indexes, and -decoded buffers may allocate separately when an encoding needs them. - -```text -ArrayProbe::scalar_at - -> existing DynArrayData::probe_scalar dispatch - -> ArrayData retrieves ProbeCtx<'a, V's associated state> - -> OperationsVTable::probe_scalar -``` - -The adapter initializes the context with a source-bound factory, called once. Retrieving -it later uses a pointer cast with a fixed concrete type rather than an `Any` lookup. The -holder records a destructor for that exact type and drops it once, freeing a spilled -allocation and recursively dropping child probes. No pointer into inline storage is kept -across moves. - -All raw storage operations are confined to the holder. Its safety requires every access -to use the same concrete context type, including lifetimes. The probe's fixed source and -that source's vtable enforce this invariant. An invariant lifetime marker prevents erased -borrowed state from outliving the source. The holder is neither `Send` nor `Sync`, since -encoding state is not required to implement those traits. - -Tests cover inline and spilled storage, alignment, moves, borrowed state, exactly-once -initialization and destruction, default scalar behavior, bounds and nulls, lazy child -creation, repeated slot reuse, source binding, independent slots/contexts, and simultaneous -local-state and child access. Encoding follow-ups should test recursive cache reuse and -benchmark complete probe lifetimes, including preparation and teardown. diff --git a/vortex-array/src/array/mod.rs b/vortex-array/src/array/mod.rs index 2431a6a93cb..ee92fe09625 100644 --- a/vortex-array/src/array/mod.rs +++ b/vortex-array/src/array/mod.rs @@ -33,8 +33,10 @@ pub use plugin::*; mod probe; pub use probe::ArrayProbe; +pub use probe::ProbeAccess; pub use probe::ProbeChildren; -pub use probe::ProbeCtx; +pub use probe::ProbeSlot; +pub use probe::ProbeState; use probe::ProbeStorage; pub use probe::ProbeUsage; @@ -524,18 +526,15 @@ impl DynArrayData for ArrayData { ) -> VortexResult { // SAFETY: this adapter belongs to the ArrayData stored in `this`. let view = unsafe { ArrayView::new_unchecked(this, &self.data) }; - let state = state.map(|storage| { - // SAFETY: ArrayProbe fixes its source for its entire lifetime, so this storage - // is accessed only through this adapter with the same associated context type. - unsafe { - storage.get_or_init(|| { - ProbeCtx::<>::ProbeState<'a>>::new( - this, - ) - }) - } - }); - >::probe_scalar(view, index, state, ctx) + match state { + Some(storage) => storage.scalar_at(view, index, ctx), + None => >::probe_scalar( + view, + index, + ProbeAccess::Once(this), + ctx, + ), + } } } diff --git a/vortex-array/src/array/probe.rs b/vortex-array/src/array/probe.rs index 0219460d96b..e7aa1e1651c 100644 --- a/vortex-array/src/array/probe.rs +++ b/vortex-array/src/array/probe.rs @@ -3,17 +3,16 @@ //! Random scalar access with optional, encoding-specific retained state. -use std::marker::PhantomData; -use std::mem::MaybeUninit; -use std::ptr::NonNull; - use vortex_error::VortexResult; use vortex_error::vortex_ensure; use vortex_error::vortex_err; use crate::ArrayRef; +use crate::ArrayView; use crate::ExecutionCtx; +use crate::array::VTable; use crate::scalar::Scalar; +use crate::vtable::OperationsVTable; /// Whether scalar access should retain preparation for subsequent lookups. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -26,15 +25,14 @@ pub enum ProbeUsage { /// A borrowed scalar accessor that retains encoding-specific state until it is dropped. /// -/// `'a` is the borrow of the root array. Its [`ProbeCtx`] retains a child probe for each +/// `'a` is the borrow of the root array. Its [`ProbeState`] retains a child probe for each /// requested slot, so one lifetime covers the whole tree and no array handles are cloned. /// Anything an encoding builds itself, such as a decoded page or validity mask, is owned /// by its state. /// /// Construction never allocates or executes the array. Repeated access initializes the -/// encoding's context on the first in-bounds lookup. Small contexts live inline; larger or -/// more aligned contexts use a heap allocation. Child storage and decoding may allocate -/// separately, when first needed. +/// encoding's context in a Box on the first in-bounds lookup. Child storage and decoding +/// may allocate separately, when first needed. /// /// `Once` is a caching hint, not a restriction on how many times the probe may be called. /// Returned scalars own their values and can outlive the probe. Probes are local to a thread. @@ -43,18 +41,77 @@ pub struct ArrayProbe<'a> { state: Option>, } +/// Scalar access to a source array, with optional retained preparation. +pub enum ProbeAccess<'a, 'p, S> { + /// Read slots with fresh probes that retain no state. + Once(&'a ArrayRef), + /// Reuse the source's local state and child probes. + Repeated(&'p mut ProbeState<'a, S>), +} + +impl<'a, S> ProbeAccess<'a, '_, S> { + /// Access retained local state, if repeated access was requested. + pub fn state_mut(&mut self) -> Option<&mut S> { + match self { + Self::Once(_) => None, + Self::Repeated(state) => Some(state.state_mut()), + } + } + + /// Access a child slot using this context's retention policy. + pub fn slot(&mut self, slot: usize) -> VortexResult> { + match self { + Self::Once(array) => { + let child = array + .slots() + .get(slot) + .and_then(Option::as_ref) + .ok_or_else(|| vortex_err!("Probe child slot {slot} is missing"))?; + Ok(ProbeSlot::Once(child)) + } + Self::Repeated(state) => Ok(ProbeSlot::Repeated(state.child(slot)?)), + } + } +} + +/// A child accessor that either starts a fresh probe or borrows a retained probe. +pub enum ProbeSlot<'a, 'p> { + /// A source read without retained preparation. + Once(&'a ArrayRef), + /// An existing repeated-access probe. + Repeated(&'p mut ArrayProbe<'a>), +} + +impl<'a> ProbeSlot<'a, '_> { + /// The source array for this slot. + pub fn array(&self) -> &'a ArrayRef { + match self { + Self::Once(array) => array, + Self::Repeated(probe) => probe.array(), + } + } + + /// Read a scalar using fresh or retained preparation as appropriate. + pub fn execute_scalar(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { + match self { + Self::Once(array) => array.probe(ProbeUsage::Once).scalar_at(index, ctx), + Self::Repeated(probe) => probe.scalar_at(index, ctx), + } + } +} + /// Local encoding state and lazy child probes retained for one source array. /// /// The framework initializes this context once for repeated access and passes it to /// [`OperationsVTable::probe_scalar`](crate::vtable::OperationsVTable::probe_scalar). -/// One-off access receives `None` instead. `S` is the encoding's associated state type; +/// One-off access uses [`ProbeAccess::Once`] instead. `S` is the encoding's associated state type; /// it may borrow the source tree for `'a` and own any prepared resources. -pub struct ProbeCtx<'a, S> { +pub struct ProbeState<'a, S> { state: S, children: ProbeChildren<'a>, } -impl<'a, S: Default> ProbeCtx<'a, S> { +impl<'a, S: Default> ProbeState<'a, S> { pub(crate) fn new(array: &'a ArrayRef) -> Self { Self { state: S::default(), @@ -66,7 +123,7 @@ impl<'a, S: Default> ProbeCtx<'a, S> { } } -impl<'a, S> ProbeCtx<'a, S> { +impl<'a, S> ProbeState<'a, S> { /// Access the encoding's retained local state. pub fn state_mut(&mut self) -> &mut S { &mut self.state @@ -88,7 +145,7 @@ impl<'a, S> ProbeCtx<'a, S> { /// Lazy child probes bound to the slots of one source array. /// -/// Obtain this through [`ProbeCtx::parts`] when retaining a mutable borrow of local state +/// Obtain this through [`ProbeState::parts`] when retaining a mutable borrow of local state /// while accessing children. Each slot has independent state, even if two slots reference /// the same array. The slot table allocates on its first valid request; unrequested slots /// remain empty. Dropping the parent context drops every created child probe. @@ -158,83 +215,57 @@ impl<'a> ArrayProbe<'a> { } } -// An open set of external state types cannot be represented by a fixed Rust enum. Erasing -// them inline avoids a mandatory Box allocation; all raw storage operations are confined here. -#[repr(align(16))] -struct InlineStorage(MaybeUninit<[u8; 128]>); - pub(crate) struct ProbeStorage<'a> { - inline: InlineStorage, - heap: Option>, - drop_fn: Option, - // The erased state may borrow the root array tree, and the type system cannot see that - // borrow once erased. This marker ties the storage to `'a` (invariantly) so it cannot - // outlive the borrow. NonNull also keeps the erased storage !Send and !Sync, since we - // impose neither bound on state types. - _lifetime: PhantomData<&'a mut &'a ()>, + // FIXME: Consider inline storage if benchmarks justify avoiding this allocation. + probe: Option>, } -impl<'a> ProbeStorage<'a> { - fn new() -> Self { - Self { - inline: InlineStorage(MaybeUninit::uninit()), - heap: None, - drop_fn: None, - _lifetime: PhantomData, - } - } +trait RetainedProbe { + fn scalar_at(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult; +} - /// # Safety - /// - /// Every call on this storage must use the same `T`, including its lifetime parameters. - /// The owning ArrayProbe fixes its source array, and only that array's erased adapter - /// may initialize/access this storage using its associated ProbeCtx type. - pub(crate) unsafe fn get_or_init(&mut self, init: impl FnOnce() -> T) -> &mut T { - if self.drop_fn.is_none() { - let value = init(); - if size_of::() <= size_of::() - && align_of::() <= align_of::() - { - // SAFETY: the size/alignment checks make the inline region suitable for T, - // and no value has been initialized in this storage yet. - unsafe { self.inline.0.as_mut_ptr().cast::().write(value) }; - self.drop_fn = Some(drop_inline::); - } else { - self.heap = Some(NonNull::from(Box::leak(Box::new(value))).cast()); - self.drop_fn = Some(drop_heap::); - } - } - // SAFETY: initialization above, or the caller's same-T invariant, establishes a - // live T at this pointer. The exclusive storage borrow provides exclusive access. - unsafe { &mut *self.as_mut_ptr().cast::() } - } +struct EncodingProbe<'a, V: VTable> { + array: ArrayView<'a, V>, + state: ProbeState<'a, >::ProbeState<'a>>, +} - fn as_mut_ptr(&mut self) -> *mut u8 { - match self.heap { - Some(ptr) => ptr.as_ptr(), - None => self.inline.0.as_mut_ptr().cast(), - } +impl RetainedProbe for EncodingProbe<'_, V> { + fn scalar_at(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { + >::probe_scalar( + self.array, + index, + ProbeAccess::Repeated(&mut self.state), + ctx, + ) } } -impl Drop for ProbeStorage<'_> { - fn drop(&mut self) { - if let Some(drop_fn) = self.drop_fn { - // SAFETY: the drop shim was installed only after its matching T was initialized. - // No pointer into inline storage is retained across moves of ProbeStorage. - unsafe { drop_fn(self.as_mut_ptr()) }; - } +impl<'a> ProbeStorage<'a> { + fn new() -> Self { + Self { probe: None } } -} -unsafe fn drop_inline(ptr: *mut u8) { - // SAFETY: the storage installed this shim for an initialized inline T. - unsafe { ptr.cast::().drop_in_place() }; -} + fn get_or_init( + &mut self, + init: impl FnOnce() -> Box, + ) -> &mut (dyn RetainedProbe + 'a) { + self.probe.get_or_insert_with(init).as_mut() + } -unsafe fn drop_heap(ptr: *mut u8) { - // SAFETY: the storage installed this shim for a T allocated by Box::new and leaked once. - drop(unsafe { Box::from_raw(ptr.cast::()) }); + pub(crate) fn scalar_at( + &mut self, + array: ArrayView<'a, V>, + index: usize, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + self.get_or_init(|| { + Box::new(EncodingProbe { + array, + state: ProbeState::new(array.array()), + }) + }) + .scalar_at(index, ctx) + } } #[cfg(test)] diff --git a/vortex-array/src/array/probe/tests.rs b/vortex-array/src/array/probe/tests.rs index 6310e289fb3..e8913a307d1 100644 --- a/vortex-array/src/array/probe/tests.rs +++ b/vortex-array/src/array/probe/tests.rs @@ -2,73 +2,73 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors use std::cell::Cell; -use std::rc::Rc; use rstest::rstest; use vortex_error::VortexResult; -use super::ProbeCtx; +use super::ProbeAccess; +use super::ProbeState; use super::ProbeStorage; use super::ProbeUsage; +use super::RetainedProbe; +use crate::ExecutionCtx; use crate::IntoArray; use crate::VortexSessionExecute; use crate::arrays::PrimitiveArray; use crate::arrays::StructArray; +use crate::scalar::Scalar; -#[derive(Default)] -struct Tracked { - drops: Rc>, +struct TrackedProbe<'a> { + value: &'a i32, + reads: &'a Cell, + drops: &'a Cell, } -impl Drop for Tracked { - fn drop(&mut self) { - self.drops.set(self.drops.get() + 1); +impl RetainedProbe for TrackedProbe<'_> { + fn scalar_at(&mut self, _index: usize, _ctx: &mut ExecutionCtx) -> VortexResult { + self.reads.set(self.reads.get() + 1); + Ok((*self.value).into()) } } -#[derive(Default)] -struct Large { - tracked: Tracked, - padding: [[u8; 32]; 8], +impl Drop for TrackedProbe<'_> { + fn drop(&mut self) { + self.drops.set(self.drops.get() + 1); + } } -#[repr(align(64))] -#[derive(Default)] -struct Aligned(Tracked); - -#[derive(Default)] -struct Borrowed<'a>(Option<&'a usize>); - #[test] -fn inline_state_survives_moves_and_drops_once() { - let mut storage = ProbeStorage::new(); +fn borrowed_state_is_initialized_once_and_survives_moves() -> VortexResult<()> { + let value = 42; let initialized = Cell::new(0); - // SAFETY: every access to this storage uses Tracked. - let drops = Rc::clone( - &unsafe { - storage.get_or_init(|| { - initialized.set(initialized.get() + 1); - Tracked::default() - }) - } - .drops, + let reads = Cell::new(0); + let drops = Cell::new(0); + let mut ctx = crate::array_session().create_execution_ctx(); + let mut storage = ProbeStorage::new(); + let init = || -> Box { + initialized.set(initialized.get() + 1); + Box::new(TrackedProbe { + value: &value, + reads: &reads, + drops: &drops, + }) + }; + assert!(storage.probe.is_none()); + assert_eq!( + storage.get_or_init(init).scalar_at(0, &mut ctx)?, + value.into() + ); + let mut moved = (storage, ()); + assert_eq!( + moved.0.get_or_init(init).scalar_at(0, &mut ctx)?, + value.into() ); - assert!(storage.heap.is_none()); - let mut moved = (storage, 1); - // SAFETY: the moved storage still contains Tracked. - assert!(Rc::ptr_eq( - &unsafe { - moved.0.get_or_init(|| { - initialized.set(initialized.get() + 1); - Tracked::default() - }) - } - .drops, - &drops - )); - drop(moved); assert_eq!(initialized.get(), 1); + assert_eq!(reads.get(), 2); + assert_eq!(drops.get(), 0); + drop(moved); assert_eq!(drops.get(), 1); + Ok(()) } #[test] @@ -76,7 +76,7 @@ fn children_are_lazy_reused_and_bound_to_their_source_slots() -> VortexResult<() let leaf = PrimitiveArray::from_iter([10i32, 20]).into_array(); let array = StructArray::from_fields(&[("left", leaf.clone()), ("right", leaf)])?.into_array(); let mut ctx = crate::array_session().create_execution_ctx(); - let mut probe = ProbeCtx::::new(&array); + let mut probe = ProbeState::::new(&array); assert_eq!(probe.children.slots.capacity(), 0); // Slot zero is the absent struct validity; invalid requests must not allocate. assert!(probe.child(0).is_err()); @@ -91,14 +91,14 @@ fn children_are_lazy_reused_and_bound_to_their_source_slots() -> VortexResult<() child .state .as_ref() - .is_some_and(|state| state.drop_fn.is_none()) + .is_some_and(|state| state.probe.is_none()) ); assert!(child.scalar_at(2, &mut ctx).is_err()); assert!( child .state .as_ref() - .is_some_and(|state| state.drop_fn.is_none()) + .is_some_and(|state| state.probe.is_none()) ); assert_eq!(child.scalar_at(0, &mut ctx)?, 10i32.into()); *state += 1; @@ -109,7 +109,7 @@ fn children_are_lazy_reused_and_bound_to_their_source_slots() -> VortexResult<() .child(1)? .state .as_ref() - .is_some_and(|state| state.drop_fn.is_some()) + .is_some_and(|state| state.probe.is_some()) ); // Identical sources in different slots still get independent probe state. @@ -123,15 +123,15 @@ fn children_are_lazy_reused_and_bound_to_their_source_slots() -> VortexResult<() .child(2)? .state .as_ref() - .is_some_and(|state| state.drop_fn.is_none()) + .is_some_and(|state| state.probe.is_none()) ); - let mut other = ProbeCtx::::new(&array); + let mut other = ProbeState::::new(&array); assert!( other .child(1)? .state .as_ref() - .is_some_and(|state| state.drop_fn.is_none()) + .is_some_and(|state| state.probe.is_none()) ); let mut moved = (probe, ()); @@ -140,53 +140,6 @@ fn children_are_lazy_reused_and_bound_to_their_source_slots() -> VortexResult<() Ok(()) } -#[test] -fn oversized_state_spills_and_drops_once() { - let mut storage = ProbeStorage::new(); - // SAFETY: every access to this storage uses Large. - let state = unsafe { storage.get_or_init::(Large::default) }; - state.padding[7][31] = 42; - let drops = Rc::clone(&state.tracked.drops); - assert!(storage.heap.is_some()); - let mut moved = (storage, 1); - // SAFETY: the moved storage still contains Large. - assert_eq!( - unsafe { moved.0.get_or_init::(Large::default) }.padding[7][31], - 42 - ); - drop(moved); - assert_eq!(drops.get(), 1); -} - -#[test] -fn over_aligned_state_spills() { - let mut storage = ProbeStorage::new(); - // SAFETY: this storage is only used for Aligned. - let state = unsafe { storage.get_or_init::(Aligned::default) }; - assert_eq!(std::ptr::from_ref(state).addr() % 64, 0); - let drops = Rc::clone(&state.0.drops); - assert!(storage.heap.is_some()); - drop(storage); - assert_eq!(drops.get(), 1); -} - -#[test] -fn state_can_borrow_and_unit_needs_no_allocation() { - let value = 42; - let mut borrowed = ProbeStorage::new(); - // SAFETY: the storage always contains Borrowed with the same source lifetime. - unsafe { borrowed.get_or_init::>(Borrowed::default) }.0 = Some(&value); - // SAFETY: same type and source lifetime as initialization. - assert_eq!( - unsafe { borrowed.get_or_init::>(Borrowed::default) }.0, - Some(&42) - ); - let mut unit = ProbeStorage::new(); - // SAFETY: this separate storage is only used for (). - unsafe { unit.get_or_init::<()>(Default::default) }; - assert!(unit.heap.is_none()); -} - #[rstest] #[case(ProbeUsage::Once)] #[case(ProbeUsage::Repeated)] @@ -198,14 +151,14 @@ fn access_checks_bounds_and_nulls(#[case] usage: ProbeUsage) -> VortexResult<()> probe .state .as_ref() - .is_none_or(|state| state.drop_fn.is_none()) + .is_none_or(|state| state.probe.is_none()) ); assert!(probe.scalar_at(3, &mut ctx).is_err()); assert!( probe .state .as_ref() - .is_none_or(|state| state.drop_fn.is_none()) + .is_none_or(|state| state.probe.is_none()) ); for index in [2, 1, 0, 2] { assert_eq!( @@ -218,3 +171,40 @@ fn access_checks_bounds_and_nulls(#[case] usage: ProbeUsage) -> VortexResult<()> } Ok(()) } + +#[rstest] +fn slot_access_uses_context_policy( + #[values(ProbeUsage::Once, ProbeUsage::Repeated)] usage: ProbeUsage, +) -> VortexResult<()> { + let leaf = PrimitiveArray::from_option_iter([Some(10i32), None]).into_array(); + let array = StructArray::from_fields(&[("values", leaf)])?.into_array(); + let mut ctx = crate::array_session().create_execution_ctx(); + let mut retained = ProbeState::<()>::new(&array); + { + let mut probe = match usage { + ProbeUsage::Once => ProbeAccess::Once(&array), + ProbeUsage::Repeated => ProbeAccess::Repeated(&mut retained), + }; + assert!(probe.slot(0).is_err()); + assert!(probe.slot(99).is_err()); + for index in [1, 0, 1, 0] { + let mut slot = probe.slot(1)?; + assert_eq!( + slot.execute_scalar(index, &mut ctx)?, + slot.array().execute_scalar(index, &mut ctx)? + ); + } + assert!(probe.slot(1)?.execute_scalar(2, &mut ctx).is_err()); + } + match usage { + ProbeUsage::Once => assert_eq!(retained.children.slots.capacity(), 0), + ProbeUsage::Repeated => assert!( + retained + .child(1)? + .state + .as_ref() + .is_some_and(|state| state.probe.is_some()) + ), + } + Ok(()) +} diff --git a/vortex-array/src/array/vtable/operations.rs b/vortex-array/src/array/vtable/operations.rs index 883c70a1b47..4b1c97facdd 100644 --- a/vortex-array/src/array/vtable/operations.rs +++ b/vortex-array/src/array/vtable/operations.rs @@ -5,7 +5,7 @@ use vortex_error::VortexResult; use vortex_error::vortex_bail; use crate::ExecutionCtx; -use crate::ProbeCtx; +use crate::ProbeAccess; use crate::array::ArrayView; use crate::array::VTable; use crate::scalar::Scalar; @@ -22,21 +22,21 @@ pub trait OperationsVTable { /// /// Default construction should be cheap and avoid allocation or execution. Preparation /// belongs in [`Self::probe_scalar`]. `'a` is the borrow of the root array, so state may - /// retain views into its source tree. Request retained child probes through [`ProbeCtx`]. + /// retain views into its source tree. Request retained child probes through [`ProbeAccess`]. /// Use `()` when no local state is needed. type ProbeState<'a>: Default + 'a; /// Read a scalar, handling nullness and optionally retaining state for subsequent reads. /// - /// Bounds have been checked, but the row may be null. `None` requests one-off access and - /// never initializes a context; `Some` reuses local state and child probes for this source. + /// Bounds have been checked, but the row may be null. `ProbeAccess::Once` requests one-off access and + /// never initializes a context; `ProbeAccess::Repeated` reuses local state and child probes for this source. /// The scalar must retain the source's logical dtype, including nullability. /// /// The default preserves the existing scalar path without adding caching. fn probe_scalar<'a>( array: ArrayView<'a, V>, index: usize, - _probe: Option<&mut ProbeCtx<'a, Self::ProbeState<'a>>>, + _probe: ProbeAccess<'a, '_, Self::ProbeState<'a>>, ctx: &mut ExecutionCtx, ) -> VortexResult { array.array().execute_scalar(index, ctx) From 4eae75496b8112f0979278dd676b2000444f5898 Mon Sep 17 00:00:00 2001 From: Joe Isaacs Date: Mon, 14 Sep 2026 19:14:52 +0100 Subject: [PATCH 3/9] refactor(array): unify scalar probes around owned state Signed-off-by: Joe Isaacs --- encodings/alp/src/alp/ops.rs | 2 +- encodings/alp/src/alp_rd/ops.rs | 2 +- encodings/bytebool/src/array.rs | 2 +- encodings/datetime-parts/src/ops.rs | 2 +- .../src/decimal_byte_parts/mod.rs | 2 +- .../src/bitpacking/vtable/operations.rs | 2 +- .../fastlanes/src/delta/vtable/operations.rs | 2 +- .../fastlanes/src/for/vtable/operations.rs | 2 +- .../fastlanes/src/rle/vtable/operations.rs | 2 +- encodings/fastlanes/src/transposed_bool.rs | 2 +- encodings/fsst/src/ops.rs | 2 +- encodings/onpair/src/ops.rs | 2 +- encodings/parquet-variant/src/operations.rs | 2 +- encodings/pco/src/array.rs | 2 +- encodings/runend/src/ops.rs | 2 +- encodings/sequence/src/array.rs | 2 +- encodings/sparse/src/ops.rs | 2 +- encodings/zigzag/src/array.rs | 2 +- encodings/zstd/src/array.rs | 2 +- encodings/zstd/src/zstd_buffers.rs | 2 +- vortex-array/src/array/erased.rs | 40 ++- vortex-array/src/array/mod.rs | 43 ++- vortex-array/src/array/probe.rs | 302 +++++++++++------- vortex-array/src/array/probe/tests.rs | 230 +++++++++---- vortex-array/src/array/vtable/operations.rs | 22 +- .../src/arrays/bool/vtable/operations.rs | 2 +- .../src/arrays/chunked/vtable/operations.rs | 2 +- .../src/arrays/constant/vtable/operations.rs | 2 +- .../src/arrays/decimal/vtable/operations.rs | 2 +- .../src/arrays/dict/vtable/operations.rs | 2 +- .../src/arrays/extension/vtable/operations.rs | 2 +- vortex-array/src/arrays/filter/vtable.rs | 2 +- .../fixed_size_list/vtable/operations.rs | 2 +- vortex-array/src/arrays/interleave/mod.rs | 2 +- .../src/arrays/list/vtable/operations.rs | 2 +- .../src/arrays/listview/vtable/operations.rs | 2 +- .../src/arrays/map/vtable/operations.rs | 2 +- .../src/arrays/masked/vtable/operations.rs | 2 +- vortex-array/src/arrays/null/mod.rs | 2 +- .../src/arrays/patched/vtable/operations.rs | 2 +- .../src/arrays/piecewise_sequence/vtable.rs | 2 +- .../src/arrays/primitive/vtable/operations.rs | 2 +- .../src/arrays/scalar_fn/vtable/operations.rs | 2 +- vortex-array/src/arrays/shared/vtable.rs | 2 +- vortex-array/src/arrays/slice/vtable.rs | 2 +- .../src/arrays/struct_/vtable/operations.rs | 2 +- .../src/arrays/union/vtable/operations.rs | 2 +- .../src/arrays/varbin/vtable/operations.rs | 2 +- .../arrays/varbinview/vtable/operations.rs | 2 +- .../src/arrays/variant/vtable/operations.rs | 2 +- vortex-python/src/arrays/py/vtable.rs | 2 +- 51 files changed, 459 insertions(+), 270 deletions(-) diff --git a/encodings/alp/src/alp/ops.rs b/encodings/alp/src/alp/ops.rs index 8b80ee9a875..58b5ee99360 100644 --- a/encodings/alp/src/alp/ops.rs +++ b/encodings/alp/src/alp/ops.rs @@ -15,7 +15,7 @@ use crate::ALPFloat; use crate::match_each_alp_float_ptype; impl OperationsVTable for ALP { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, ALP>, diff --git a/encodings/alp/src/alp_rd/ops.rs b/encodings/alp/src/alp_rd/ops.rs index 40749c51557..1433966a01e 100644 --- a/encodings/alp/src/alp_rd/ops.rs +++ b/encodings/alp/src/alp_rd/ops.rs @@ -14,7 +14,7 @@ use crate::ALPRDArrayExt; use crate::ALPRDArraySlotsExt; impl OperationsVTable for ALPRD { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, ALPRD>, diff --git a/encodings/bytebool/src/array.rs b/encodings/bytebool/src/array.rs index 1236ebdd5e7..02bb4fcb2b8 100644 --- a/encodings/bytebool/src/array.rs +++ b/encodings/bytebool/src/array.rs @@ -312,7 +312,7 @@ impl ValidityVTable for ByteBool { } impl OperationsVTable for ByteBool { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, ByteBool>, diff --git a/encodings/datetime-parts/src/ops.rs b/encodings/datetime-parts/src/ops.rs index e3be4a1b0ff..0ee4f9e315c 100644 --- a/encodings/datetime-parts/src/ops.rs +++ b/encodings/datetime-parts/src/ops.rs @@ -17,7 +17,7 @@ use crate::timestamp; use crate::timestamp::TimestampParts; impl OperationsVTable for DateTimeParts { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, DateTimeParts>, diff --git a/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs b/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs index 62c0eea25cb..e1d081ddaca 100644 --- a/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs +++ b/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs @@ -289,7 +289,7 @@ fn to_canonical_decimal( } impl OperationsVTable for DecimalByteParts { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, DecimalByteParts>, diff --git a/encodings/fastlanes/src/bitpacking/vtable/operations.rs b/encodings/fastlanes/src/bitpacking/vtable/operations.rs index 4c4be1e746f..2816407ac03 100644 --- a/encodings/fastlanes/src/bitpacking/vtable/operations.rs +++ b/encodings/fastlanes/src/bitpacking/vtable/operations.rs @@ -11,7 +11,7 @@ use crate::BitPacked; use crate::bitpack_decompress; use crate::bitpacking::array::BitPackedArrayExt; impl OperationsVTable for BitPacked { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, BitPacked>, diff --git a/encodings/fastlanes/src/delta/vtable/operations.rs b/encodings/fastlanes/src/delta/vtable/operations.rs index 999fad1b33c..b37760621ea 100644 --- a/encodings/fastlanes/src/delta/vtable/operations.rs +++ b/encodings/fastlanes/src/delta/vtable/operations.rs @@ -11,7 +11,7 @@ use vortex_error::VortexResult; use super::Delta; impl OperationsVTable for Delta { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Delta>, diff --git a/encodings/fastlanes/src/for/vtable/operations.rs b/encodings/fastlanes/src/for/vtable/operations.rs index 3df84a704a9..361020824d5 100644 --- a/encodings/fastlanes/src/for/vtable/operations.rs +++ b/encodings/fastlanes/src/for/vtable/operations.rs @@ -13,7 +13,7 @@ use super::FoR; use crate::r#for::array::FoRArrayExt; use crate::r#for::array::FoRArraySlotsExt; impl OperationsVTable for FoR { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, FoR>, diff --git a/encodings/fastlanes/src/rle/vtable/operations.rs b/encodings/fastlanes/src/rle/vtable/operations.rs index 8045fc550cc..ba6a624f1f5 100644 --- a/encodings/fastlanes/src/rle/vtable/operations.rs +++ b/encodings/fastlanes/src/rle/vtable/operations.rs @@ -14,7 +14,7 @@ use crate::rle::RLEArrayExt; use crate::rle::RLEArraySlotsExt; impl OperationsVTable for RLE { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, RLE>, diff --git a/encodings/fastlanes/src/transposed_bool.rs b/encodings/fastlanes/src/transposed_bool.rs index 3acd47772b8..92da3b3b79c 100644 --- a/encodings/fastlanes/src/transposed_bool.rs +++ b/encodings/fastlanes/src/transposed_bool.rs @@ -256,7 +256,7 @@ impl VTable for TransposedBool { } impl OperationsVTable for TransposedBool { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, TransposedBool>, diff --git a/encodings/fsst/src/ops.rs b/encodings/fsst/src/ops.rs index 615dc78d6db..03560b2c8b8 100644 --- a/encodings/fsst/src/ops.rs +++ b/encodings/fsst/src/ops.rs @@ -14,7 +14,7 @@ use crate::FSST; use crate::FSSTArrayExt; impl OperationsVTable for FSST { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, FSST>, diff --git a/encodings/onpair/src/ops.rs b/encodings/onpair/src/ops.rs index a78df1d4b7d..082bc55e199 100644 --- a/encodings/onpair/src/ops.rs +++ b/encodings/onpair/src/ops.rs @@ -18,7 +18,7 @@ use crate::decode::code_boundary_at; use crate::decode::collect_widened; impl OperationsVTable for OnPair { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, OnPair>, diff --git a/encodings/parquet-variant/src/operations.rs b/encodings/parquet-variant/src/operations.rs index b8579516228..eab80bf63e1 100644 --- a/encodings/parquet-variant/src/operations.rs +++ b/encodings/parquet-variant/src/operations.rs @@ -31,7 +31,7 @@ use crate::ParquetVariantArraySlotsExt; use crate::vtable::ParquetVariant; impl OperationsVTable for ParquetVariant { - type ProbeState<'a> = (); + type ProbeState = (); /// Resolves one row according to the Parquet Variant shredding rules. /// diff --git a/encodings/pco/src/array.rs b/encodings/pco/src/array.rs index 48939f9efe8..97a17fe6a3c 100644 --- a/encodings/pco/src/array.rs +++ b/encodings/pco/src/array.rs @@ -778,7 +778,7 @@ impl ValidityVTable for Pco { } impl OperationsVTable for Pco { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Pco>, diff --git a/encodings/runend/src/ops.rs b/encodings/runend/src/ops.rs index 14acef6c3e1..46949d84e1b 100644 --- a/encodings/runend/src/ops.rs +++ b/encodings/runend/src/ops.rs @@ -18,7 +18,7 @@ use crate::array::RunEndArrayExt; use crate::array::RunEndArraySlotsExt; impl OperationsVTable for RunEnd { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, RunEnd>, diff --git a/encodings/sequence/src/array.rs b/encodings/sequence/src/array.rs index 477da3191fe..e61eb54e4eb 100644 --- a/encodings/sequence/src/array.rs +++ b/encodings/sequence/src/array.rs @@ -426,7 +426,7 @@ impl VTable for Sequence { } impl OperationsVTable for Sequence { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Sequence>, diff --git a/encodings/sparse/src/ops.rs b/encodings/sparse/src/ops.rs index e5c45e47911..acabbb103d5 100644 --- a/encodings/sparse/src/ops.rs +++ b/encodings/sparse/src/ops.rs @@ -11,7 +11,7 @@ use crate::Sparse; use crate::SparseExt as _; impl OperationsVTable for Sparse { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Sparse>, diff --git a/encodings/zigzag/src/array.rs b/encodings/zigzag/src/array.rs index 4a7dc8b7436..09076fcbbdf 100644 --- a/encodings/zigzag/src/array.rs +++ b/encodings/zigzag/src/array.rs @@ -231,7 +231,7 @@ impl Default for ZigZagData { } impl OperationsVTable for ZigZag { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, ZigZag>, diff --git a/encodings/zstd/src/array.rs b/encodings/zstd/src/array.rs index 66d0940de1e..dafe8e820a9 100644 --- a/encodings/zstd/src/array.rs +++ b/encodings/zstd/src/array.rs @@ -1588,7 +1588,7 @@ impl ValidityVTable for Zstd { } impl OperationsVTable for Zstd { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Zstd>, diff --git a/encodings/zstd/src/zstd_buffers.rs b/encodings/zstd/src/zstd_buffers.rs index 912af56dd71..51d338777ca 100644 --- a/encodings/zstd/src/zstd_buffers.rs +++ b/encodings/zstd/src/zstd_buffers.rs @@ -520,7 +520,7 @@ impl VTable for ZstdBuffers { } impl OperationsVTable for ZstdBuffers { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, ZstdBuffers>, diff --git a/vortex-array/src/array/erased.rs b/vortex-array/src/array/erased.rs index 76c0b33cd18..510744201bc 100644 --- a/vortex-array/src/array/erased.rs +++ b/vortex-array/src/array/erased.rs @@ -21,11 +21,13 @@ use crate::AnyCanonical; use crate::Array; use crate::ArrayEq; use crate::ArrayHash; +use crate::ArrayProbe; use crate::ArrayView; use crate::Canonical; use crate::ExecutionCtx; use crate::ExecutionResult; use crate::IntoArray; +use crate::ProbeUsage; use crate::VTable; use crate::VortexSessionExecute; use crate::aggregate_fn::fns::sum::sum; @@ -273,28 +275,32 @@ impl ArrayRef { } /// Execute the array to extract a scalar at the given index. + // TODO(joe): deprecate this. pub fn execute_scalar(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { - vortex_ensure!(index < self.len(), OutOfBounds: index, 0, self.len()); - if self.dtype().is_nullable() && self.is_invalid(index, ctx)? { - return Ok(Scalar::null(self.dtype().clone())); - } - let scalar = self.0.data.execute_scalar(self, index, ctx)?; - debug_assert_eq!(self.dtype(), scalar.dtype(), "Scalar dtype mismatch"); - Ok(scalar) + self.probe(ProbeUsage::Once).execute_scalar(index, ctx) + } + + /// Create an accessor with the requested policy for retaining state between scalar lookups. + /// + /// ``` + /// use vortex_array::{IntoArray, ProbeUsage, VortexSessionExecute}; + /// use vortex_array::arrays::PrimitiveArray; + /// + /// let array = PrimitiveArray::from_iter([10i32, 20, 30]).into_array(); + /// let mut ctx = vortex_array::array_session().create_execution_ctx(); + /// let mut probe = array.probe(ProbeUsage::Repeated); + /// assert_eq!(probe.execute_scalar(2, &mut ctx)?, 30i32.into()); + /// assert_eq!(probe.execute_scalar(0, &mut ctx)?, 10i32.into()); + /// # Ok::<(), vortex_error::VortexError>(()) + /// ``` + pub fn probe(&self, usage: ProbeUsage) -> ArrayProbe { + ArrayProbe::new(self.clone(), usage) } /// Returns whether the item at `index` is valid. + // TODO(joe): deprecate this. pub fn is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { - vortex_ensure!(index < self.len(), OutOfBounds: index, 0, self.len()); - match self.validity()? { - Validity::NonNullable | Validity::AllValid => Ok(true), - Validity::AllInvalid => Ok(false), - Validity::Array(a) => a - .execute_scalar(index, ctx)? - .as_bool() - .value() - .ok_or_else(|| vortex_err!("validity value at index {} is null", index)), - } + self.probe(ProbeUsage::Once).execute_is_valid(index, ctx) } /// Returns whether the item at `index` is invalid. diff --git a/vortex-array/src/array/mod.rs b/vortex-array/src/array/mod.rs index ee92fe09625..aa36aebc620 100644 --- a/vortex-array/src/array/mod.rs +++ b/vortex-array/src/array/mod.rs @@ -35,10 +35,12 @@ mod probe; pub use probe::ArrayProbe; pub use probe::ProbeAccess; pub use probe::ProbeChildren; +pub use probe::ProbeCtx; pub use probe::ProbeSlot; pub use probe::ProbeState; use probe::ProbeStorage; pub use probe::ProbeUsage; +pub use probe::ValidityProbe; mod foreign; pub(crate) use foreign::*; @@ -229,21 +231,19 @@ pub(crate) trait DynArrayData: 'static + private::Sealed + Send + Sync + Debug { ctx: &mut ExecutionCtx, ) -> VortexResult; - /// Execute the scalar at the given index. - /// - /// This method panics if the index is out of bounds for the array. - fn execute_scalar( + fn probe_is_valid( &self, this: &ArrayRef, index: usize, + state: Option<&mut ProbeStorage>, ctx: &mut ExecutionCtx, - ) -> VortexResult; + ) -> VortexResult; - fn probe_scalar<'a>( - &'a self, - this: &'a ArrayRef, + fn probe_scalar( + &self, + this: &ArrayRef, index: usize, - state: Option<&mut ProbeStorage<'a>>, + state: Option<&mut ProbeStorage>, ctx: &mut ExecutionCtx, ) -> VortexResult; } @@ -507,21 +507,30 @@ impl DynArrayData for ArrayData { V::execute(typed, ctx) } - fn execute_scalar( + fn probe_is_valid( &self, this: &ArrayRef, index: usize, + state: Option<&mut ProbeStorage>, ctx: &mut ExecutionCtx, - ) -> VortexResult { - let view = unsafe { ArrayView::new_unchecked(this, &self.data) }; - >::scalar_at(view, index, ctx) + ) -> VortexResult { + let mut validity = match state { + Some(storage) => storage + .validity::<>::ProbeState>(this)?, + None => ValidityProbe::once(this)?, + }; + validity + .execute_scalar(index, ctx)? + .as_bool() + .value() + .ok_or_else(|| vortex_err!("validity value at index {index} is null")) } - fn probe_scalar<'a>( - &'a self, - this: &'a ArrayRef, + fn probe_scalar( + &self, + this: &ArrayRef, index: usize, - state: Option<&mut ProbeStorage<'a>>, + state: Option<&mut ProbeStorage>, ctx: &mut ExecutionCtx, ) -> VortexResult { // SAFETY: this adapter belongs to the ArrayData stored in `this`. diff --git a/vortex-array/src/array/probe.rs b/vortex-array/src/array/probe.rs index e7aa1e1651c..ab5232c7592 100644 --- a/vortex-array/src/array/probe.rs +++ b/vortex-array/src/array/probe.rs @@ -3,6 +3,8 @@ //! Random scalar access with optional, encoding-specific retained state. +use std::any::Any; + use vortex_error::VortexResult; use vortex_error::vortex_ensure; use vortex_error::vortex_err; @@ -12,6 +14,7 @@ use crate::ArrayView; use crate::ExecutionCtx; use crate::array::VTable; use crate::scalar::Scalar; +use crate::validity::Validity; use crate::vtable::OperationsVTable; /// Whether scalar access should retain preparation for subsequent lookups. @@ -23,68 +26,79 @@ pub enum ProbeUsage { Repeated, } -/// A borrowed scalar accessor that retains encoding-specific state until it is dropped. -/// -/// `'a` is the borrow of the root array. Its [`ProbeState`] retains a child probe for each -/// requested slot, so one lifetime covers the whole tree and no array handles are cloned. -/// Anything an encoding builds itself, such as a decoded page or validity mask, is owned -/// by its state. +/// A scalar accessor that owns its source and retains preparation between lookups. /// -/// Construction never allocates or executes the array. Repeated access initializes the -/// encoding's context in a Box on the first in-bounds lookup. Child storage and decoding -/// may allocate separately, when first needed. -/// -/// `Once` is a caching hint, not a restriction on how many times the probe may be called. -/// Returned scalars own their values and can outlive the probe. Probes are local to a thread. -pub struct ArrayProbe<'a> { - array: &'a ArrayRef, - state: Option>, +/// Array handles share their buffers. Repeated access initializes encoding state and +/// child probes lazily; following the same slots reuses the same preparation. +/// `Once` retains no preparation. The probe can outlive the original array handle. +/// Probes are local to a thread. +pub struct ArrayProbe { + array: ArrayRef, + state: Option, } /// Scalar access to a source array, with optional retained preparation. -pub enum ProbeAccess<'a, 'p, S> { +pub enum ProbeAccess<'p, S> { /// Read slots with fresh probes that retain no state. - Once(&'a ArrayRef), + Once(&'p ArrayRef), /// Reuse the source's local state and child probes. - Repeated(&'p mut ProbeState<'a, S>), + Repeated(ProbeCtx<'p, S>), +} + +/// Access to retained preparation, independent of source ownership. +pub struct ProbeCtx<'p, S> { + state: &'p mut S, + children: &'p mut ProbeChildren, + validity: &'p mut Option, } -impl<'a, S> ProbeAccess<'a, '_, S> { +impl ProbeAccess<'_, S> { /// Access retained local state, if repeated access was requested. pub fn state_mut(&mut self) -> Option<&mut S> { match self { Self::Once(_) => None, - Self::Repeated(state) => Some(state.state_mut()), + Self::Repeated(probe) => Some(probe.state), + } + } + + /// Access validity using this context's retention policy. + /// + /// Array-backed validity is read lazily through an owned probe; repeated access retains it. + pub fn validity(&mut self) -> VortexResult> { + match self { + Self::Once(array) => ValidityProbe::once(array), + Self::Repeated(probe) => validity_access(&probe.children.array, probe.validity), } } /// Access a child slot using this context's retention policy. - pub fn slot(&mut self, slot: usize) -> VortexResult> { + /// + /// Returns `None` for an absent slot and an error for an out-of-bounds slot. + pub fn slot(&mut self, slot: usize) -> VortexResult>> { match self { Self::Once(array) => { let child = array .slots() .get(slot) - .and_then(Option::as_ref) - .ok_or_else(|| vortex_err!("Probe child slot {slot} is missing"))?; - Ok(ProbeSlot::Once(child)) + .ok_or_else(|| vortex_err!("Probe slot {slot} is out of bounds"))?; + Ok(child.as_ref().map(ProbeSlot::Once)) } - Self::Repeated(state) => Ok(ProbeSlot::Repeated(state.child(slot)?)), + Self::Repeated(probe) => Ok(probe.children.slot(slot)?.map(ProbeSlot::Repeated)), } } } /// A child accessor that either starts a fresh probe or borrows a retained probe. -pub enum ProbeSlot<'a, 'p> { +pub enum ProbeSlot<'p> { /// A source read without retained preparation. - Once(&'a ArrayRef), + Once(&'p ArrayRef), /// An existing repeated-access probe. - Repeated(&'p mut ArrayProbe<'a>), + Repeated(&'p mut ArrayProbe), } -impl<'a> ProbeSlot<'a, '_> { +impl ProbeSlot<'_> { /// The source array for this slot. - pub fn array(&self) -> &'a ArrayRef { + pub fn array(&self) -> &ArrayRef { match self { Self::Once(array) => array, Self::Repeated(probe) => probe.array(), @@ -94,8 +108,8 @@ impl<'a> ProbeSlot<'a, '_> { /// Read a scalar using fresh or retained preparation as appropriate. pub fn execute_scalar(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { match self { - Self::Once(array) => array.probe(ProbeUsage::Once).scalar_at(index, ctx), - Self::Repeated(probe) => probe.scalar_at(index, ctx), + Self::Once(array) => array.probe(ProbeUsage::Once).execute_scalar(index, ctx), + Self::Repeated(probe) => probe.execute_scalar(index, ctx), } } } @@ -105,40 +119,43 @@ impl<'a> ProbeSlot<'a, '_> { /// The framework initializes this context once for repeated access and passes it to /// [`OperationsVTable::probe_scalar`](crate::vtable::OperationsVTable::probe_scalar). /// One-off access uses [`ProbeAccess::Once`] instead. `S` is the encoding's associated state type; -/// it may borrow the source tree for `'a` and own any prepared resources. -pub struct ProbeState<'a, S> { +/// it owns its prepared resources, including shared buffer or array handles. +pub struct ProbeState { state: S, - children: ProbeChildren<'a>, + children: ProbeChildren, + validity: Option, } -impl<'a, S: Default> ProbeState<'a, S> { - pub(crate) fn new(array: &'a ArrayRef) -> Self { +impl ProbeState { + pub(crate) fn new(array: &ArrayRef) -> Self { Self { state: S::default(), children: ProbeChildren { - array, + array: array.clone(), slots: Vec::new(), }, + validity: None, } } } -impl<'a, S> ProbeState<'a, S> { +impl ProbeState { + /// Borrow retained preparation and children for scalar execution. + pub fn access(&mut self) -> ProbeAccess<'_, S> { + ProbeAccess::Repeated(ProbeCtx { + state: &mut self.state, + children: &mut self.children, + validity: &mut self.validity, + }) + } + /// Access the encoding's retained local state. pub fn state_mut(&mut self) -> &mut S { &mut self.state } - /// Get the retained probe for a source slot, creating it on the first request. - /// - /// Returns an error for an absent or out-of-bounds slot. Creating a child probe does - /// not execute the child or initialize its encoding state. - pub fn child(&mut self, slot: usize) -> VortexResult<&mut ArrayProbe<'a>> { - self.children.child(slot) - } - /// Borrow local state and child access together, allowing disjoint mutable access. - pub fn parts(&mut self) -> (&mut S, &mut ProbeChildren<'a>) { + pub fn parts(&mut self) -> (&mut S, &mut ProbeChildren) { (&mut self.state, &mut self.children) } } @@ -149,122 +166,189 @@ impl<'a, S> ProbeState<'a, S> { /// while accessing children. Each slot has independent state, even if two slots reference /// the same array. The slot table allocates on its first valid request; unrequested slots /// remain empty. Dropping the parent context drops every created child probe. -pub struct ProbeChildren<'a> { - array: &'a ArrayRef, - slots: Vec>>, +pub struct ProbeChildren { + array: ArrayRef, + slots: Vec>, } -impl<'a> ProbeChildren<'a> { +impl ProbeChildren { /// Get or create a repeated-access probe for the given source slot. /// - /// Returns an error for an absent or out-of-bounds slot without allocating a slot table. - pub fn child(&mut self, slot: usize) -> VortexResult<&mut ArrayProbe<'a>> { + /// Returns `None` for an absent slot and an error for an out-of-bounds slot. + /// Neither case allocates a slot table. + pub fn slot(&mut self, slot: usize) -> VortexResult> { let child = self .array .slots() .get(slot) - .and_then(Option::as_ref) - .ok_or_else(|| vortex_err!("Probe child slot {slot} is missing"))?; + .ok_or_else(|| vortex_err!("Probe slot {slot} is out of bounds"))?; + let Some(child) = child else { + return Ok(None); + }; if self.slots.is_empty() { self.slots.resize_with(self.array.slots().len(), || None); } - Ok(self.slots[slot].get_or_insert_with(|| child.probe(ProbeUsage::Repeated))) + Ok(Some( + self.slots[slot].get_or_insert_with(|| child.probe(ProbeUsage::Repeated)), + )) } } -impl ArrayRef { - /// Create an accessor with the requested policy for retaining state between scalar lookups. - /// - /// ``` - /// use vortex_array::{IntoArray, ProbeUsage, VortexSessionExecute}; - /// use vortex_array::arrays::PrimitiveArray; - /// - /// let array = PrimitiveArray::from_iter([10i32, 20, 30]).into_array(); - /// let mut ctx = vortex_array::array_session().create_execution_ctx(); - /// let mut probe = array.probe(ProbeUsage::Repeated); - /// assert_eq!(probe.scalar_at(2, &mut ctx)?, 30i32.into()); - /// assert_eq!(probe.scalar_at(0, &mut ctx)?, 10i32.into()); - /// # Ok::<(), vortex_error::VortexError>(()) - /// ``` - pub fn probe(&self, usage: ProbeUsage) -> ArrayProbe<'_> { - ArrayProbe { - array: self, +impl ArrayProbe { + /// Own an array and choose whether to retain preparation. + pub fn new(array: ArrayRef, usage: ProbeUsage) -> Self { + Self { + array, state: match usage { ProbeUsage::Once => None, ProbeUsage::Repeated => Some(ProbeStorage::new()), }, } } -} -impl<'a> ArrayProbe<'a> { /// The array this probe reads from. - pub fn array(&self) -> &'a ArrayRef { - self.array + pub fn array(&self) -> &ArrayRef { + &self.array } /// Read a scalar, including its nullness, preparing and reusing state as appropriate. - pub fn scalar_at(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { + pub fn execute_scalar(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { vortex_ensure!(index < self.array.len(), OutOfBounds: index, 0, self.array.len()); + if self.array.dtype().is_nullable() && !self.execute_is_valid(index, ctx)? { + return Ok(Scalar::null(self.array.dtype().clone())); + } let scalar = self.array .dyn_array() - .probe_scalar(self.array, index, self.state.as_mut(), ctx)?; + .probe_scalar(&self.array, index, self.state.as_mut(), ctx)?; debug_assert_eq!(scalar.dtype(), self.array.dtype(), "Scalar dtype mismatch"); Ok(scalar) } + + /// Check bounds and read validity using this probe's retention policy. + pub fn execute_is_valid(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { + vortex_ensure!(index < self.array.len(), OutOfBounds: index, 0, self.array.len()); + if !self.array.dtype().is_nullable() { + return Ok(true); + } + self.array + .dyn_array() + .probe_is_valid(&self.array, index, self.state.as_mut(), ctx) + } } -pub(crate) struct ProbeStorage<'a> { - // FIXME: Consider inline storage if benchmarks justify avoiding this allocation. - probe: Option>, +/// A validity accessor with the parent probe's retention policy. +pub struct ValidityProbe<'p> { + len: usize, + inner: ValidityAccess<'p>, } -trait RetainedProbe { - fn scalar_at(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult; +enum ValidityAccess<'p> { + Once(Validity), + Repeated(&'p mut ProbeValidity), } -struct EncodingProbe<'a, V: VTable> { - array: ArrayView<'a, V>, - state: ProbeState<'a, >::ProbeState<'a>>, +impl ValidityProbe<'_> { + pub(super) fn once(array: &ArrayRef) -> VortexResult { + Ok(Self { + len: array.len(), + inner: ValidityAccess::Once(array.validity()?), + }) + } + + /// Read a non-null boolean scalar indicating whether the requested row is valid. + pub fn execute_scalar(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { + vortex_ensure!(index < self.len, OutOfBounds: index, 0, self.len); + match &mut self.inner { + ValidityAccess::Once(validity) => match validity { + Validity::NonNullable | Validity::AllValid => Ok(true.into()), + Validity::AllInvalid => Ok(false.into()), + Validity::Array(array) => array.execute_scalar(index, ctx), + }, + ValidityAccess::Repeated(validity) => validity.execute_scalar(index, ctx), + } + } } -impl RetainedProbe for EncodingProbe<'_, V> { - fn scalar_at(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { - >::probe_scalar( - self.array, - index, - ProbeAccess::Repeated(&mut self.state), - ctx, - ) +fn validity_access<'p>( + array: &ArrayRef, + slot: &'p mut Option, +) -> VortexResult> { + let validity = match slot { + Some(validity) => validity, + slot @ None => slot.insert(ProbeValidity::new(array.validity()?, ProbeUsage::Repeated)), + }; + Ok(ValidityProbe { + len: array.len(), + inner: ValidityAccess::Repeated(validity), + }) +} + +enum ProbeValidity { + Constant(bool), + Array(ArrayProbe), +} + +impl ProbeValidity { + fn new(validity: Validity, usage: ProbeUsage) -> Self { + match validity { + Validity::NonNullable | Validity::AllValid => Self::Constant(true), + Validity::AllInvalid => Self::Constant(false), + Validity::Array(array) => Self::Array(ArrayProbe::new(array, usage)), + } + } + + fn execute_scalar(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { + match self { + Self::Constant(valid) => Ok((*valid).into()), + Self::Array(probe) => probe.execute_scalar(index, ctx), + } } } -impl<'a> ProbeStorage<'a> { +pub(crate) struct ProbeStorage { + // FIXME: Consider inline storage if benchmarks justify avoiding this allocation. + probe: Option>, +} + +impl ProbeStorage { fn new() -> Self { Self { probe: None } } - fn get_or_init( + fn get_or_init( &mut self, - init: impl FnOnce() -> Box, - ) -> &mut (dyn RetainedProbe + 'a) { - self.probe.get_or_insert_with(init).as_mut() + array: &ArrayRef, + ) -> VortexResult<&mut ProbeState> { + self.probe + .get_or_insert_with(|| Box::new(ProbeState::::new(array))) + .downcast_mut::>() + .ok_or_else(|| vortex_err!("Probe state type mismatch")) + } + + pub(crate) fn validity( + &mut self, + array: &ArrayRef, + ) -> VortexResult> { + let state = self.get_or_init::(array)?; + validity_access(array, &mut state.validity) } pub(crate) fn scalar_at( &mut self, - array: ArrayView<'a, V>, + array: ArrayView<'_, V>, index: usize, ctx: &mut ExecutionCtx, ) -> VortexResult { - self.get_or_init(|| { - Box::new(EncodingProbe { - array, - state: ProbeState::new(array.array()), - }) - }) - .scalar_at(index, ctx) + let state = self.get_or_init::<>::ProbeState>( + array.array(), + )?; + >::probe_scalar( + array, + index, + state.access(), + ctx, + ) } } diff --git a/vortex-array/src/array/probe/tests.rs b/vortex-array/src/array/probe/tests.rs index e8913a307d1..4c50de3672b 100644 --- a/vortex-array/src/array/probe/tests.rs +++ b/vortex-array/src/array/probe/tests.rs @@ -2,69 +2,47 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors use std::cell::Cell; +use std::rc::Rc; use rstest::rstest; use vortex_error::VortexResult; +use vortex_error::vortex_err; use super::ProbeAccess; use super::ProbeState; use super::ProbeStorage; use super::ProbeUsage; -use super::RetainedProbe; -use crate::ExecutionCtx; +use crate::ArrayRef; use crate::IntoArray; use crate::VortexSessionExecute; use crate::arrays::PrimitiveArray; use crate::arrays::StructArray; use crate::scalar::Scalar; -struct TrackedProbe<'a> { - value: &'a i32, - reads: &'a Cell, - drops: &'a Cell, +#[derive(Default)] +struct TrackedState { + reads: usize, + drops: Rc>, } -impl RetainedProbe for TrackedProbe<'_> { - fn scalar_at(&mut self, _index: usize, _ctx: &mut ExecutionCtx) -> VortexResult { - self.reads.set(self.reads.get() + 1); - Ok((*self.value).into()) - } -} - -impl Drop for TrackedProbe<'_> { +impl Drop for TrackedState { fn drop(&mut self) { self.drops.set(self.drops.get() + 1); } } #[test] -fn borrowed_state_is_initialized_once_and_survives_moves() -> VortexResult<()> { - let value = 42; - let initialized = Cell::new(0); - let reads = Cell::new(0); - let drops = Cell::new(0); - let mut ctx = crate::array_session().create_execution_ctx(); +fn state_is_initialized_once_and_survives_moves() -> VortexResult<()> { + let array = PrimitiveArray::from_iter([42i32]).into_array(); let mut storage = ProbeStorage::new(); - let init = || -> Box { - initialized.set(initialized.get() + 1); - Box::new(TrackedProbe { - value: &value, - reads: &reads, - drops: &drops, - }) - }; assert!(storage.probe.is_none()); - assert_eq!( - storage.get_or_init(init).scalar_at(0, &mut ctx)?, - value.into() - ); + let state = storage.get_or_init::(&array)?; + state.state.reads += 1; + let drops = Rc::clone(&state.state.drops); let mut moved = (storage, ()); - assert_eq!( - moved.0.get_or_init(init).scalar_at(0, &mut ctx)?, - value.into() - ); - assert_eq!(initialized.get(), 1); - assert_eq!(reads.get(), 2); + let state = moved.0.get_or_init::(&array)?; + assert_eq!(state.state.reads, 1); + assert!(Rc::ptr_eq(&state.state.drops, &drops)); assert_eq!(drops.get(), 0); drop(moved); assert_eq!(drops.get(), 1); @@ -79,48 +57,70 @@ fn children_are_lazy_reused_and_bound_to_their_source_slots() -> VortexResult<() let mut probe = ProbeState::::new(&array); assert_eq!(probe.children.slots.capacity(), 0); // Slot zero is the absent struct validity; invalid requests must not allocate. - assert!(probe.child(0).is_err()); - assert!(probe.child(99).is_err()); + assert!(probe.children.slot(0)?.is_none()); + assert!(probe.children.slot(99).is_err()); assert_eq!(probe.children.slots.capacity(), 0); - let first = std::ptr::from_mut(probe.child(1)?); + let first = std::ptr::from_mut( + probe + .children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))?, + ); assert!(probe.children.slots[2].is_none()); let (state, children) = probe.parts(); - let child = children.child(1)?; - assert!( - child - .state - .as_ref() - .is_some_and(|state| state.probe.is_none()) - ); - assert!(child.scalar_at(2, &mut ctx).is_err()); - assert!( - child - .state - .as_ref() - .is_some_and(|state| state.probe.is_none()) - ); - assert_eq!(child.scalar_at(0, &mut ctx)?, 10i32.into()); + let child = children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))?; + assert!(child.execute_scalar(2, &mut ctx).is_err()); + assert_eq!(child.execute_scalar(0, &mut ctx)?, 10i32.into()); *state += 1; assert_eq!(*probe.state_mut(), 1); - assert_eq!(std::ptr::from_mut(probe.child(1)?), first); + assert_eq!( + std::ptr::from_mut( + probe + .children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? + ), + first + ); assert!( probe - .child(1)? + .children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? .state .as_ref() .is_some_and(|state| state.probe.is_some()) ); // Identical sources in different slots still get independent probe state. - assert_ne!(std::ptr::from_mut(probe.child(2)?), first); + assert_ne!( + std::ptr::from_mut( + probe + .children + .slot(2)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? + ), + first + ); let source = array.slots()[1] .as_ref() .ok_or_else(|| vortex_error::vortex_err!("missing fixture slot"))?; - assert!(std::ptr::eq(probe.child(1)?.array(), source)); + assert!(ArrayRef::ptr_eq( + probe + .children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? + .array(), + source + )); assert!( probe - .child(2)? + .children + .slot(2)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? .state .as_ref() .is_some_and(|state| state.probe.is_none()) @@ -128,15 +128,34 @@ fn children_are_lazy_reused_and_bound_to_their_source_slots() -> VortexResult<() let mut other = ProbeState::::new(&array); assert!( other - .child(1)? + .children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? .state .as_ref() .is_some_and(|state| state.probe.is_none()) ); let mut moved = (probe, ()); - assert_eq!(std::ptr::from_mut(moved.0.child(1)?), first); - assert_eq!(moved.0.child(1)?.scalar_at(1, &mut ctx)?, 20i32.into()); + assert_eq!( + std::ptr::from_mut( + moved + .0 + .children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? + ), + first + ); + assert_eq!( + moved + .0 + .children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? + .execute_scalar(1, &mut ctx)?, + 20i32.into() + ); Ok(()) } @@ -153,16 +172,30 @@ fn access_checks_bounds_and_nulls(#[case] usage: ProbeUsage) -> VortexResult<()> .as_ref() .is_none_or(|state| state.probe.is_none()) ); - assert!(probe.scalar_at(3, &mut ctx).is_err()); + assert!(probe.execute_scalar(3, &mut ctx).is_err()); assert!( probe .state .as_ref() .is_none_or(|state| state.probe.is_none()) ); + assert!(probe.execute_scalar(1, &mut ctx)?.is_null()); + assert_eq!( + probe + .state + .as_ref() + .is_some_and(|state| state.probe.is_some()), + usage == ProbeUsage::Repeated + ); + assert_eq!( + array.execute_scalar(0, &mut ctx)?, + Scalar::primitive(10i32, array.dtype().nullability()) + ); + assert!(array.execute_scalar(1, &mut ctx)?.is_null()); + assert!(array.execute_scalar(3, &mut ctx).is_err()); for index in [2, 1, 0, 2] { assert_eq!( - probe.scalar_at(index, &mut ctx)?, + probe.execute_scalar(index, &mut ctx)?, array.execute_scalar(index, &mut ctx)? ); } @@ -183,24 +216,34 @@ fn slot_access_uses_context_policy( { let mut probe = match usage { ProbeUsage::Once => ProbeAccess::Once(&array), - ProbeUsage::Repeated => ProbeAccess::Repeated(&mut retained), + ProbeUsage::Repeated => retained.access(), }; - assert!(probe.slot(0).is_err()); + assert!(probe.slot(0)?.is_none()); assert!(probe.slot(99).is_err()); for index in [1, 0, 1, 0] { - let mut slot = probe.slot(1)?; + let mut slot = probe + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))?; assert_eq!( slot.execute_scalar(index, &mut ctx)?, slot.array().execute_scalar(index, &mut ctx)? ); } - assert!(probe.slot(1)?.execute_scalar(2, &mut ctx).is_err()); + assert!( + probe + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? + .execute_scalar(2, &mut ctx) + .is_err() + ); } match usage { ProbeUsage::Once => assert_eq!(retained.children.slots.capacity(), 0), ProbeUsage::Repeated => assert!( retained - .child(1)? + .children + .slot(1)? + .ok_or_else(|| vortex_err!("missing fixture slot"))? .state .as_ref() .is_some_and(|state| state.probe.is_some()) @@ -208,3 +251,48 @@ fn slot_access_uses_context_policy( } Ok(()) } + +#[test] +fn validity_is_owned_by_probe_state() -> VortexResult<()> { + let array = PrimitiveArray::from_option_iter([Some(10i32), None]).into_array(); + let mut ctx = crate::array_session().create_execution_ctx(); + let mut state = ProbeState::<()>::new(&array); + assert!(state.validity.is_none()); + assert_eq!( + state.access().validity()?.execute_scalar(1, &mut ctx)?, + false.into() + ); + assert_eq!(state.children.slots.capacity(), 0); + let mut validity = state + .validity + .take() + .ok_or_else(|| vortex_err!("missing validity probe"))?; + drop(state); + drop(array); + assert_eq!(validity.execute_scalar(0, &mut ctx)?, true.into()); + assert_eq!(validity.execute_scalar(1, &mut ctx)?, false.into()); + Ok(()) +} + +#[rstest] +fn validity_accessor_obeys_retention_policy( + #[values(ProbeUsage::Once, ProbeUsage::Repeated)] usage: ProbeUsage, +) -> VortexResult<()> { + let array = PrimitiveArray::from_option_iter([Some(10i32), None]).into_array(); + let mut ctx = crate::array_session().create_execution_ctx(); + let mut retained = ProbeState::<()>::new(&array); + for index in [0, 1, 0] { + let mut access = match usage { + ProbeUsage::Once => ProbeAccess::Once(&array), + ProbeUsage::Repeated => retained.access(), + }; + assert_eq!( + access.validity()?.execute_scalar(index, &mut ctx)?, + (index == 0).into() + ); + assert!(access.validity()?.execute_scalar(2, &mut ctx).is_err()); + } + assert_eq!(retained.validity.is_some(), usage == ProbeUsage::Repeated); + assert_eq!(retained.children.slots.capacity(), 0); + Ok(()) +} diff --git a/vortex-array/src/array/vtable/operations.rs b/vortex-array/src/array/vtable/operations.rs index 4b1c97facdd..f044af0ee33 100644 --- a/vortex-array/src/array/vtable/operations.rs +++ b/vortex-array/src/array/vtable/operations.rs @@ -21,27 +21,29 @@ pub trait OperationsVTable { /// Encoding-specific state retained by repeated scalar access. /// /// Default construction should be cheap and avoid allocation or execution. Preparation - /// belongs in [`Self::probe_scalar`]. `'a` is the borrow of the root array, so state may - /// retain views into its source tree. Request retained child probes through [`ProbeAccess`]. + /// belongs in [`Self::probe_scalar`]. State owns its preparation and may retain shared + /// buffer or array handles. Request retained child probes through [`ProbeAccess`]. /// Use `()` when no local state is needed. - type ProbeState<'a>: Default + 'a; + type ProbeState: Default + 'static; - /// Read a scalar, handling nullness and optionally retaining state for subsequent reads. + /// Read a non-null scalar, optionally retaining state for subsequent reads. /// - /// Bounds have been checked, but the row may be null. `ProbeAccess::Once` requests one-off access and + /// Bounds and validity have been checked; the row is non-null. `ProbeAccess::Once` requests one-off access and /// never initializes a context; `ProbeAccess::Repeated` reuses local state and child probes for this source. /// The scalar must retain the source's logical dtype, including nullability. /// /// The default preserves the existing scalar path without adding caching. - fn probe_scalar<'a>( - array: ArrayView<'a, V>, + fn probe_scalar( + array: ArrayView<'_, V>, index: usize, - _probe: ProbeAccess<'a, '_, Self::ProbeState<'a>>, + _probe: ProbeAccess<'_, Self::ProbeState>, ctx: &mut ExecutionCtx, ) -> VortexResult { - array.array().execute_scalar(index, ctx) + // FIXME: Remove this default once all encodings have migrated to probe_scalar. + Self::scalar_at(array, index, ctx) } + // FIXME: Deprecate scalar_at once encodings have migrated to probe_scalar. /// Fetch the scalar at the given index. /// /// ## Preconditions @@ -60,7 +62,7 @@ pub trait OperationsVTable { } impl OperationsVTable for NotSupported { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, V>, diff --git a/vortex-array/src/arrays/bool/vtable/operations.rs b/vortex-array/src/arrays/bool/vtable/operations.rs index 5a91469eae5..e1ec02ecbc8 100644 --- a/vortex-array/src/arrays/bool/vtable/operations.rs +++ b/vortex-array/src/arrays/bool/vtable/operations.rs @@ -11,7 +11,7 @@ use crate::arrays::bool::BoolArrayExt; use crate::scalar::Scalar; impl OperationsVTable for Bool { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Bool>, diff --git a/vortex-array/src/arrays/chunked/vtable/operations.rs b/vortex-array/src/arrays/chunked/vtable/operations.rs index d5484a3a538..395e8aff70a 100644 --- a/vortex-array/src/arrays/chunked/vtable/operations.rs +++ b/vortex-array/src/arrays/chunked/vtable/operations.rs @@ -11,7 +11,7 @@ use crate::arrays::chunked::ChunkedArrayExt; use crate::scalar::Scalar; impl OperationsVTable for Chunked { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Chunked>, diff --git a/vortex-array/src/arrays/constant/vtable/operations.rs b/vortex-array/src/arrays/constant/vtable/operations.rs index d7ada7ee274..9c49094f324 100644 --- a/vortex-array/src/arrays/constant/vtable/operations.rs +++ b/vortex-array/src/arrays/constant/vtable/operations.rs @@ -10,7 +10,7 @@ use crate::arrays::Constant; use crate::scalar::Scalar; impl OperationsVTable for Constant { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Constant>, diff --git a/vortex-array/src/arrays/decimal/vtable/operations.rs b/vortex-array/src/arrays/decimal/vtable/operations.rs index 2eae2715b73..aecf6638bd2 100644 --- a/vortex-array/src/arrays/decimal/vtable/operations.rs +++ b/vortex-array/src/arrays/decimal/vtable/operations.rs @@ -12,7 +12,7 @@ use crate::scalar::DecimalValue; use crate::scalar::Scalar; impl OperationsVTable for Decimal { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Decimal>, diff --git a/vortex-array/src/arrays/dict/vtable/operations.rs b/vortex-array/src/arrays/dict/vtable/operations.rs index a127c2febad..d497db0f2f8 100644 --- a/vortex-array/src/arrays/dict/vtable/operations.rs +++ b/vortex-array/src/arrays/dict/vtable/operations.rs @@ -12,7 +12,7 @@ use crate::arrays::dict::DictArraySlotsExt; use crate::scalar::Scalar; impl OperationsVTable for Dict { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Dict>, diff --git a/vortex-array/src/arrays/extension/vtable/operations.rs b/vortex-array/src/arrays/extension/vtable/operations.rs index b6843612955..519ef6088f7 100644 --- a/vortex-array/src/arrays/extension/vtable/operations.rs +++ b/vortex-array/src/arrays/extension/vtable/operations.rs @@ -11,7 +11,7 @@ use crate::arrays::extension::ExtensionArrayExt; use crate::scalar::Scalar; impl OperationsVTable for Extension { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Extension>, diff --git a/vortex-array/src/arrays/filter/vtable.rs b/vortex-array/src/arrays/filter/vtable.rs index 8301b682fd9..7f0e0bfd014 100644 --- a/vortex-array/src/arrays/filter/vtable.rs +++ b/vortex-array/src/arrays/filter/vtable.rs @@ -200,7 +200,7 @@ impl VTable for Filter { } } impl OperationsVTable for Filter { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Filter>, diff --git a/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs b/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs index e31892e0215..eca0e45ad95 100644 --- a/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs +++ b/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs @@ -11,7 +11,7 @@ use crate::arrays::fixed_size_list::FixedSizeListArrayExt; use crate::scalar::Scalar; impl OperationsVTable for FixedSizeList { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, FixedSizeList>, diff --git a/vortex-array/src/arrays/interleave/mod.rs b/vortex-array/src/arrays/interleave/mod.rs index 94fa0d77796..fb649cf6e19 100644 --- a/vortex-array/src/arrays/interleave/mod.rs +++ b/vortex-array/src/arrays/interleave/mod.rs @@ -389,7 +389,7 @@ impl VTable for Interleave { } impl OperationsVTable for Interleave { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Interleave>, diff --git a/vortex-array/src/arrays/list/vtable/operations.rs b/vortex-array/src/arrays/list/vtable/operations.rs index 688e635c3b0..668cc18a32f 100644 --- a/vortex-array/src/arrays/list/vtable/operations.rs +++ b/vortex-array/src/arrays/list/vtable/operations.rs @@ -13,7 +13,7 @@ use crate::arrays::list::ListArrayExt; use crate::scalar::Scalar; impl OperationsVTable for List { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, List>, diff --git a/vortex-array/src/arrays/listview/vtable/operations.rs b/vortex-array/src/arrays/listview/vtable/operations.rs index ec83518eae7..8608985463d 100644 --- a/vortex-array/src/arrays/listview/vtable/operations.rs +++ b/vortex-array/src/arrays/listview/vtable/operations.rs @@ -13,7 +13,7 @@ use crate::arrays::listview::ListViewArrayExt; use crate::scalar::Scalar; impl OperationsVTable for ListView { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, ListView>, diff --git a/vortex-array/src/arrays/map/vtable/operations.rs b/vortex-array/src/arrays/map/vtable/operations.rs index bb7be8274d0..66b8e47a75a 100644 --- a/vortex-array/src/arrays/map/vtable/operations.rs +++ b/vortex-array/src/arrays/map/vtable/operations.rs @@ -13,7 +13,7 @@ use crate::arrays::struct_::StructArrayExt; use crate::scalar::Scalar; impl OperationsVTable for Map { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Map>, diff --git a/vortex-array/src/arrays/masked/vtable/operations.rs b/vortex-array/src/arrays/masked/vtable/operations.rs index 673738520b2..a418e23f217 100644 --- a/vortex-array/src/arrays/masked/vtable/operations.rs +++ b/vortex-array/src/arrays/masked/vtable/operations.rs @@ -11,7 +11,7 @@ use crate::arrays::masked::MaskedArraySlotsExt; use crate::scalar::Scalar; impl OperationsVTable for Masked { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Masked>, diff --git a/vortex-array/src/arrays/null/mod.rs b/vortex-array/src/arrays/null/mod.rs index 057188a2e16..b4d4536c9cf 100644 --- a/vortex-array/src/arrays/null/mod.rs +++ b/vortex-array/src/arrays/null/mod.rs @@ -175,7 +175,7 @@ impl Array { } impl OperationsVTable for Null { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( _array: ArrayView<'_, Null>, diff --git a/vortex-array/src/arrays/patched/vtable/operations.rs b/vortex-array/src/arrays/patched/vtable/operations.rs index 2135943164e..bd7440965ab 100644 --- a/vortex-array/src/arrays/patched/vtable/operations.rs +++ b/vortex-array/src/arrays/patched/vtable/operations.rs @@ -14,7 +14,7 @@ use crate::optimizer::ArrayOptimizer; use crate::scalar::Scalar; impl OperationsVTable for Patched { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Patched>, diff --git a/vortex-array/src/arrays/piecewise_sequence/vtable.rs b/vortex-array/src/arrays/piecewise_sequence/vtable.rs index a40d8f1e8a1..d39bd6eccac 100644 --- a/vortex-array/src/arrays/piecewise_sequence/vtable.rs +++ b/vortex-array/src/arrays/piecewise_sequence/vtable.rs @@ -145,7 +145,7 @@ impl VTable for PiecewiseSequence { } impl OperationsVTable for PiecewiseSequence { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, PiecewiseSequence>, diff --git a/vortex-array/src/arrays/primitive/vtable/operations.rs b/vortex-array/src/arrays/primitive/vtable/operations.rs index 7cc7e5a47a6..9501513fb1c 100644 --- a/vortex-array/src/arrays/primitive/vtable/operations.rs +++ b/vortex-array/src/arrays/primitive/vtable/operations.rs @@ -11,7 +11,7 @@ use crate::match_each_native_ptype; use crate::scalar::Scalar; impl OperationsVTable for Primitive { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Primitive>, diff --git a/vortex-array/src/arrays/scalar_fn/vtable/operations.rs b/vortex-array/src/arrays/scalar_fn/vtable/operations.rs index 75a2875cb82..21af5728572 100644 --- a/vortex-array/src/arrays/scalar_fn/vtable/operations.rs +++ b/vortex-array/src/arrays/scalar_fn/vtable/operations.rs @@ -15,7 +15,7 @@ use crate::scalar::Scalar; use crate::scalar_fn::VecExecutionArgs; impl OperationsVTable for ScalarFn { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, ScalarFn>, diff --git a/vortex-array/src/arrays/shared/vtable.rs b/vortex-array/src/arrays/shared/vtable.rs index 9d097d7914f..b1a9a4d7b15 100644 --- a/vortex-array/src/arrays/shared/vtable.rs +++ b/vortex-array/src/arrays/shared/vtable.rs @@ -125,7 +125,7 @@ impl VTable for Shared { } } impl OperationsVTable for Shared { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Shared>, diff --git a/vortex-array/src/arrays/slice/vtable.rs b/vortex-array/src/arrays/slice/vtable.rs index 8aa7d65f21e..c09d81ee4f1 100644 --- a/vortex-array/src/arrays/slice/vtable.rs +++ b/vortex-array/src/arrays/slice/vtable.rs @@ -169,7 +169,7 @@ impl VTable for Slice { } } impl OperationsVTable for Slice { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Slice>, diff --git a/vortex-array/src/arrays/struct_/vtable/operations.rs b/vortex-array/src/arrays/struct_/vtable/operations.rs index 35b70fbeef5..54d086bf7c9 100644 --- a/vortex-array/src/arrays/struct_/vtable/operations.rs +++ b/vortex-array/src/arrays/struct_/vtable/operations.rs @@ -12,7 +12,7 @@ use crate::scalar::Scalar; use crate::scalar::ScalarValue; impl OperationsVTable for Struct { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Struct>, diff --git a/vortex-array/src/arrays/union/vtable/operations.rs b/vortex-array/src/arrays/union/vtable/operations.rs index 2da1e623f90..78b9759ab1c 100644 --- a/vortex-array/src/arrays/union/vtable/operations.rs +++ b/vortex-array/src/arrays/union/vtable/operations.rs @@ -14,7 +14,7 @@ use crate::arrays::union::UnionArraySlotsExt; use crate::scalar::Scalar; impl OperationsVTable for Union { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Union>, diff --git a/vortex-array/src/arrays/varbin/vtable/operations.rs b/vortex-array/src/arrays/varbin/vtable/operations.rs index 4f2ada9c948..cc5090d8912 100644 --- a/vortex-array/src/arrays/varbin/vtable/operations.rs +++ b/vortex-array/src/arrays/varbin/vtable/operations.rs @@ -12,7 +12,7 @@ use crate::arrays::varbin::varbin_scalar; use crate::scalar::Scalar; impl OperationsVTable for VarBin { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, VarBin>, diff --git a/vortex-array/src/arrays/varbinview/vtable/operations.rs b/vortex-array/src/arrays/varbinview/vtable/operations.rs index 1e48be20c9f..53866d29fb5 100644 --- a/vortex-array/src/arrays/varbinview/vtable/operations.rs +++ b/vortex-array/src/arrays/varbinview/vtable/operations.rs @@ -11,7 +11,7 @@ use crate::arrays::varbin::varbin_scalar; use crate::scalar::Scalar; impl OperationsVTable for VarBinView { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, VarBinView>, diff --git a/vortex-array/src/arrays/variant/vtable/operations.rs b/vortex-array/src/arrays/variant/vtable/operations.rs index eabf6ba842b..ed61223d637 100644 --- a/vortex-array/src/arrays/variant/vtable/operations.rs +++ b/vortex-array/src/arrays/variant/vtable/operations.rs @@ -12,7 +12,7 @@ use crate::arrays::variant::VariantArraySlotsExt; use crate::scalar::Scalar; impl OperationsVTable for Variant { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( array: ArrayView<'_, Variant>, diff --git a/vortex-python/src/arrays/py/vtable.rs b/vortex-python/src/arrays/py/vtable.rs index edb9156ccf0..5697d5a7574 100644 --- a/vortex-python/src/arrays/py/vtable.rs +++ b/vortex-python/src/arrays/py/vtable.rs @@ -122,7 +122,7 @@ impl VTable for PythonVTable { } impl OperationsVTable for PythonVTable { - type ProbeState<'a> = (); + type ProbeState = (); fn scalar_at( _array: ArrayView<'_, PythonVTable>, From 1d886e3e970697f1d8261f023ebfcd368b4850a7 Mon Sep 17 00:00:00 2001 From: Joe Isaacs Date: Mon, 14 Sep 2026 19:03:35 +0000 Subject: [PATCH 4/9] docs(array): drop redundant explicit link target on probe_scalar `OperationsVTable` is already imported in scope, so the explicit `crate::vtable::...` target duplicates the label and trips `rustdoc::redundant_explicit_links` under `-D warnings`. Signed-off-by: Joe Isaacs Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FEKHRLFEn9Tc7mHcRG7Hc9 --- vortex-array/src/array/probe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vortex-array/src/array/probe.rs b/vortex-array/src/array/probe.rs index ab5232c7592..7421f619b13 100644 --- a/vortex-array/src/array/probe.rs +++ b/vortex-array/src/array/probe.rs @@ -117,7 +117,7 @@ impl ProbeSlot<'_> { /// Local encoding state and lazy child probes retained for one source array. /// /// The framework initializes this context once for repeated access and passes it to -/// [`OperationsVTable::probe_scalar`](crate::vtable::OperationsVTable::probe_scalar). +/// [`OperationsVTable::probe_scalar`]. /// One-off access uses [`ProbeAccess::Once`] instead. `S` is the encoding's associated state type; /// it owns its prepared resources, including shared buffer or array handles. pub struct ProbeState { From d52fc4a837a666325ef3606204dcf3230aa3afde Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 19:08:33 +0000 Subject: [PATCH 5/9] refactor(array): deprecate one-off scalar and validity access `ArrayRef::probe` supersedes the one-off scalar and validity accessors: a probe performs the same bounds and validity checks but can retain encoding preparation across lookups, which the one-shot methods cannot. Deprecate `execute_scalar`, `is_valid`, and `is_invalid` on both `ArrayRef` and `Array`, and repoint the already-deprecated `scalar_at` notes at `probe` rather than at `execute_scalar`. Add `Array::::probe` so the typed handle has the replacement the notes name. On the encoding side, deprecate `OperationsVTable::scalar_at` in favour of `probe_scalar`, which receives a `ProbeAccess` for retained state. The default `probe_scalar` still forwards to it during migration. Signed-off-by: "Joe Isaacs" Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMRyEJHyh72KkM8o9paBeP --- vortex-array/src/array/erased.rs | 20 +++++++++++++---- vortex-array/src/array/typed.rs | 25 +++++++++++++++++++-- vortex-array/src/array/vtable/operations.rs | 6 ++++- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/vortex-array/src/array/erased.rs b/vortex-array/src/array/erased.rs index 510744201bc..fba1f44e56b 100644 --- a/vortex-array/src/array/erased.rs +++ b/vortex-array/src/array/erased.rs @@ -266,8 +266,8 @@ impl ArrayRef { /// Fetch the scalar at the given index. #[deprecated( - note = "Use `execute_scalar` instead, which allows passing an execution context for more \ - efficient execution when fetching multiple scalars from the same array." + note = "Use `ArrayRef::probe` instead, which takes an execution context and retains \ + encoding state across lookups: `array.probe(ProbeUsage::Once).execute_scalar(index, ctx)`." )] #[allow(clippy::disallowed_methods)] pub fn scalar_at(&self, index: usize) -> VortexResult { @@ -275,7 +275,11 @@ impl ArrayRef { } /// Execute the array to extract a scalar at the given index. - // TODO(joe): deprecate this. + #[deprecated( + note = "Use `ArrayRef::probe` instead, which retains encoding state across lookups: \ + `array.probe(ProbeUsage::Once).execute_scalar(index, ctx)`, or `ProbeUsage::Repeated` \ + when reading more than one index from the same array." + )] pub fn execute_scalar(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { self.probe(ProbeUsage::Once).execute_scalar(index, ctx) } @@ -298,12 +302,20 @@ impl ArrayRef { } /// Returns whether the item at `index` is valid. - // TODO(joe): deprecate this. + #[deprecated( + note = "Use `ArrayRef::probe` instead, which retains validity state across lookups: \ + `array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)`, or `ProbeUsage::Repeated` \ + when reading more than one index from the same array." + )] pub fn is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { self.probe(ProbeUsage::Once).execute_is_valid(index, ctx) } /// Returns whether the item at `index` is invalid. + #[deprecated( + note = "Use `ArrayRef::probe` instead: \ + `!array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)?`." + )] pub fn is_invalid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { Ok(!self.is_valid(index, ctx)?) } diff --git a/vortex-array/src/array/typed.rs b/vortex-array/src/array/typed.rs index a9def0318bf..940a2cdfa3a 100644 --- a/vortex-array/src/array/typed.rs +++ b/vortex-array/src/array/typed.rs @@ -19,10 +19,12 @@ use std::sync::Arc; use vortex_error::VortexResult; +use crate::ArrayProbe; use crate::ArrayRef; use crate::ArraySlots; use crate::ExecutionCtx; use crate::IntoArray; +use crate::ProbeUsage; use crate::VortexSessionExecute; use crate::array::ArrayId; use crate::array::ArrayView; @@ -381,8 +383,8 @@ impl Array { } #[deprecated( - note = "Use `execute_scalar` instead, which allows passing an execution context for more \ - efficient execution when fetching multiple scalars from the same array." + note = "Use `Array::::probe` instead, which takes an execution context and retains \ + encoding state across lookups: `array.probe(ProbeUsage::Once).execute_scalar(index, ctx)`." )] #[allow(clippy::disallowed_methods)] pub fn scalar_at(&self, index: usize) -> VortexResult { @@ -391,6 +393,11 @@ impl Array { } /// Execute the array to extract a scalar at the given index. + #[deprecated( + note = "Use `Array::::probe` instead, which retains encoding state across lookups: \ + `array.probe(ProbeUsage::Once).execute_scalar(index, ctx)`, or `ProbeUsage::Repeated` \ + when reading more than one index from the same array." + )] pub fn execute_scalar( &self, index: usize, @@ -399,6 +406,11 @@ impl Array { self.inner.execute_scalar(index, ctx) } + /// Create an accessor with the requested policy for retaining state between scalar lookups. + pub fn probe(&self, usage: ProbeUsage) -> ArrayProbe { + self.inner.probe(usage) + } + /// Filter the array with a selection mask. pub fn filter(&self, mask: vortex_mask::Mask) -> VortexResult { self.inner.filter(mask) @@ -415,11 +427,20 @@ impl Array { } /// Returns whether `index` is valid using the provided execution context. + #[deprecated( + note = "Use `Array::::probe` instead, which retains validity state across lookups: \ + `array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)`, or `ProbeUsage::Repeated` \ + when reading more than one index from the same array." + )] pub fn is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { self.inner.is_valid(index, ctx) } /// Returns whether `index` is null using the provided execution context. + #[deprecated( + note = "Use `Array::::probe` instead: \ + `!array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)?`." + )] pub fn is_invalid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { self.inner.is_invalid(index, ctx) } diff --git a/vortex-array/src/array/vtable/operations.rs b/vortex-array/src/array/vtable/operations.rs index f044af0ee33..385410feb7f 100644 --- a/vortex-array/src/array/vtable/operations.rs +++ b/vortex-array/src/array/vtable/operations.rs @@ -43,7 +43,6 @@ pub trait OperationsVTable { Self::scalar_at(array, index, ctx) } - // FIXME: Deprecate scalar_at once encodings have migrated to probe_scalar. /// Fetch the scalar at the given index. /// /// ## Preconditions @@ -54,6 +53,11 @@ pub trait OperationsVTable { /// ## Postconditions /// /// The returned [`Scalar`] must have the same logical dtype as the array's element dtype. + // FIXME: Remove this hook once all encodings have migrated to probe_scalar. + #[deprecated( + note = "Implement `OperationsVTable::probe_scalar` instead, which is handed a \ + `ProbeAccess` so the encoding can retain preparation across lookups." + )] fn scalar_at( array: ArrayView<'_, V>, index: usize, From 0ff4cd294da5386bbe397f06ba3a7b7ae91100b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 20:31:50 +0000 Subject: [PATCH 6/9] refactor: expect the deprecated scalar and validity accessors at each site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deprecating the one-off scalar and validity accessors leaves several hundred call sites warning across the workspace, and CI runs clippy with `-D warnings`. Annotate each site with `#[expect(deprecated)]` rather than allowing the lint globally, so every suppression is visible in the code that needs it and goes unfulfilled once that caller moves to `ArrayRef::probe` — the lint then points at the annotation to delete. Sites were located by the compiler rather than by grep: each crate was checked with `--all-targets --all-features`, the `deprecated` diagnostics parsed from the JSON output, and an annotation placed on the enclosing item. Three cases need something other than the enclosing function: - `#[rstest]` discards lint attributes from its expansion, so attributes between `#[case]` and `fn`, above `#[rstest]`, inside the body, and on individual statements all leave the warning firing. Those files take a `#![expect(deprecated)]` at file or `mod tests` scope instead. - A `macro_rules!` body is linted at its definition site, so the annotation belongs in the macro. One in `assert_nth_scalar!` clears the warnings it caused in five downstream encoding crates. - A deprecated item using another deprecated item still warns, so `scalar_at` and `is_invalid` carry their own annotation. Also reflows the two `#[deprecated]` notes from the previous commit that rustfmt wanted on one line. Signed-off-by: "Joe Isaacs" Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMRyEJHyh72KkM8o9paBeP --- encodings/alp/src/alp/ops.rs | 1 + encodings/alp/src/alp_rd/ops.rs | 1 + encodings/bytebool/src/array.rs | 1 + encodings/datetime-parts/src/ops.rs | 1 + .../src/decimal_byte_parts/mod.rs | 2 ++ .../src/bitpacking/array/bitpack_decompress.rs | 2 ++ .../fastlanes/src/bitpacking/compute/take.rs | 1 + .../src/bitpacking/vtable/operations.rs | 2 ++ .../fastlanes/src/delta/array/delta_compress.rs | 1 + .../fastlanes/src/delta/vtable/operations.rs | 3 +++ .../fastlanes/src/for/array/for_compress.rs | 2 ++ encodings/fastlanes/src/for/vtable/operations.rs | 1 + encodings/fastlanes/src/rle/array/mod.rs | 2 ++ encodings/fastlanes/src/rle/vtable/operations.rs | 4 ++++ encodings/fastlanes/src/transposed_bool.rs | 2 ++ encodings/fsst/src/array.rs | 1 + encodings/fsst/src/ops.rs | 1 + encodings/onpair/src/decode.rs | 1 + encodings/onpair/src/ops.rs | 1 + encodings/onpair/src/tests.rs | 3 +++ encodings/parquet-variant/src/array.rs | 1 + encodings/parquet-variant/src/arrow.rs | 1 + .../parquet-variant/src/json_to_variant_tests.rs | 5 +++++ encodings/parquet-variant/src/kernel.rs | 15 ++++++++++++--- encodings/parquet-variant/src/operations.rs | 10 ++++++++++ encodings/pco/src/array.rs | 1 + encodings/runend/src/array.rs | 2 ++ encodings/runend/src/compute/cast.rs | 1 + encodings/runend/src/kernel.rs | 1 + encodings/runend/src/ops.rs | 2 ++ encodings/sequence/src/array.rs | 3 +++ encodings/sequence/src/compute/cast.rs | 1 + encodings/sequence/src/model_tests.rs | 1 + encodings/sparse/src/compute/between.rs | 1 + encodings/sparse/src/compute/is_constant.rs | 1 + encodings/sparse/src/lib.rs | 5 +++++ encodings/zigzag/src/array.rs | 2 ++ encodings/zigzag/src/compute/mod.rs | 1 + encodings/zstd/src/array.rs | 1 + encodings/zstd/src/test.rs | 1 + encodings/zstd/src/zstd_buffers.rs | 2 ++ fuzz/src/array/compare.rs | 1 + fuzz/src/array/fill_null.rs | 1 + fuzz/src/array/filter.rs | 1 + fuzz/src/array/mask.rs | 5 +++++ fuzz/src/array/mod.rs | 3 +++ fuzz/src/array/scalar_at.rs | 1 + fuzz/src/array/search_sorted.rs | 1 + fuzz/src/array/slice.rs | 1 + fuzz/src/array/sort.rs | 1 + fuzz/src/array/take.rs | 1 + fuzz/src/gpu/mod.rs | 1 + vortex-array/benches/scalar_at_struct.rs | 2 ++ .../aggregate_fn/fns/all_non_distinct/tests.rs | 1 + .../aggregate_fn/fns/all_non_distinct/variant.rs | 1 + vortex-array/src/aggregate_fn/fns/first/mod.rs | 1 + .../src/aggregate_fn/fns/is_constant/mod.rs | 2 ++ .../src/aggregate_fn/fns/is_sorted/mod.rs | 3 +++ vortex-array/src/aggregate_fn/fns/last/mod.rs | 1 + vortex-array/src/aggregate_fn/fns/mean/mod.rs | 1 + vortex-array/src/aggregate_fn/fns/sum/grouped.rs | 2 ++ vortex-array/src/array/erased.rs | 8 ++++---- vortex-array/src/array/probe.rs | 1 + vortex-array/src/array/probe/tests.rs | 2 ++ vortex-array/src/array/typed.rs | 10 ++++++---- vortex-array/src/array/vtable/operations.rs | 1 + vortex-array/src/arrays/assertions.rs | 6 +++++- vortex-array/src/arrays/bool/array.rs | 3 +++ vortex-array/src/arrays/chunked/tests.rs | 1 + .../src/arrays/chunked/vtable/canonical.rs | 3 +++ .../src/arrays/chunked/vtable/operations.rs | 1 + vortex-array/src/arrays/constant/compute/cast.rs | 1 + .../src/arrays/constant/vtable/canonical.rs | 5 +++++ .../src/arrays/constant/vtable/operations.rs | 1 + vortex-array/src/arrays/decimal/compute/cast.rs | 2 ++ .../src/arrays/decimal/vtable/operations.rs | 1 + .../src/arrays/dict/vtable/operations.rs | 1 + .../src/arrays/extension/vtable/operations.rs | 1 + .../src/arrays/filter/execute/listview.rs | 1 + vortex-array/src/arrays/filter/vtable.rs | 1 + .../src/arrays/fixed_size_list/tests/basic.rs | 3 +++ .../arrays/fixed_size_list/tests/degenerate.rs | 6 ++++++ .../src/arrays/fixed_size_list/tests/nested.rs | 3 +++ .../arrays/fixed_size_list/tests/nullability.rs | 6 ++++++ .../src/arrays/fixed_size_list/tests/take.rs | 2 ++ .../arrays/fixed_size_list/vtable/operations.rs | 1 + vortex-array/src/arrays/interleave/mod.rs | 2 ++ vortex-array/src/arrays/list/array.rs | 2 ++ vortex-array/src/arrays/list/compute/take.rs | 7 +++++++ vortex-array/src/arrays/list/tests.rs | 8 ++++++++ .../src/arrays/list/vtable/operations.rs | 1 + vortex-array/src/arrays/listview/array.rs | 2 ++ vortex-array/src/arrays/listview/rebuild.rs | 2 ++ vortex-array/src/arrays/listview/tests/basic.rs | 2 ++ vortex-array/src/arrays/listview/tests/filter.rs | 1 + vortex-array/src/arrays/listview/tests/nested.rs | 3 +++ .../src/arrays/listview/tests/nullability.rs | 2 ++ .../src/arrays/listview/tests/operations.rs | 7 +++++++ vortex-array/src/arrays/listview/tests/take.rs | 1 + .../src/arrays/listview/vtable/operations.rs | 1 + vortex-array/src/arrays/map/tests.rs | 5 +++++ vortex-array/src/arrays/map/vtable/operations.rs | 1 + vortex-array/src/arrays/masked/tests.rs | 1 + .../src/arrays/masked/vtable/canonical.rs | 1 + .../src/arrays/masked/vtable/operations.rs | 1 + vortex-array/src/arrays/null/compute/cast.rs | 1 + vortex-array/src/arrays/null/compute/mod.rs | 1 + vortex-array/src/arrays/patched/array.rs | 1 + .../src/arrays/patched/vtable/operations.rs | 4 ++++ .../src/arrays/piecewise_sequence/tests.rs | 1 + .../src/arrays/scalar_fn/vtable/operations.rs | 1 + vortex-array/src/arrays/shared/vtable.rs | 1 + vortex-array/src/arrays/slice/vtable.rs | 1 + .../src/arrays/struct_/vtable/operations.rs | 1 + vortex-array/src/arrays/union/tests/mod.rs | 7 +++++++ vortex-array/src/arrays/union/tests/take.rs | 1 + .../src/arrays/union/vtable/operations.rs | 1 + vortex-array/src/arrays/varbin/array.rs | 1 + vortex-array/src/arrays/varbin/builder.rs | 1 + .../src/arrays/varbin/vtable/canonical.rs | 1 + vortex-array/src/arrays/variant/mod.rs | 2 ++ vortex-array/src/arrays/variant/vtable/kernel.rs | 1 + .../src/arrays/variant/vtable/operations.rs | 1 + vortex-array/src/builders/decimal.rs | 2 ++ vortex-array/src/builders/fixed_size_list.rs | 1 + vortex-array/src/builders/list.rs | 2 ++ vortex-array/src/builders/primitive.rs | 2 ++ vortex-array/src/builders/tests.rs | 2 ++ vortex-array/src/builders/varbinview.rs | 1 + .../src/compute/conformance/binary_numeric.rs | 1 + vortex-array/src/compute/conformance/cast.rs | 5 +++++ .../src/compute/conformance/consistency.rs | 16 ++++++++++++++++ vortex-array/src/compute/conformance/filter.rs | 3 +++ vortex-array/src/compute/conformance/mask.rs | 8 ++++++++ vortex-array/src/compute/conformance/take.rs | 11 +++++++++++ vortex-array/src/display/mod.rs | 1 + vortex-array/src/patches.rs | 9 +++++++++ vortex-array/src/scalar_fn/fns/binary/boolean.rs | 1 + .../src/scalar_fn/fns/binary/compare/tests.rs | 2 ++ vortex-array/src/scalar_fn/fns/binary/mod.rs | 1 + vortex-array/src/scalar_fn/fns/byte_length.rs | 2 ++ vortex-array/src/scalar_fn/fns/case_when.rs | 2 ++ vortex-array/src/scalar_fn/fns/is_not_null.rs | 4 ++++ vortex-array/src/scalar_fn/fns/is_null.rs | 4 ++++ .../src/scalar_fn/fns/list_contains/mod.rs | 7 +++++++ vortex-array/src/scalar_fn/fns/list_length.rs | 1 + vortex-array/src/scalar_fn/fns/list_sum.rs | 4 ++++ .../src/scalar_fn/fns/variant_get/mod.rs | 3 +++ .../unstable/row/batch/execute/constant.rs | 1 + vortex-array/src/search_sorted/mod.rs | 1 + vortex-array/src/search_sorted/primitive.rs | 2 ++ vortex-array/src/validity.rs | 1 + vortex-arrow/src/datum.rs | 1 + vortex-btrblocks/src/schemes/patches.rs | 1 + vortex-compressor/src/compressor/constant.rs | 1 + vortex-datafusion/src/convert/scalars.rs | 1 + vortex-duckdb/src/exporter/run_end.rs | 1 + vortex-ffi/src/array.rs | 2 ++ vortex-ffi/src/expression.rs | 1 + vortex-file/src/pruning.rs | 1 + vortex-layout/src/layouts/list/reader.rs | 1 + vortex-layout/src/layouts/struct_/reader.rs | 2 ++ vortex-python/src/arrays/mod.rs | 1 + vortex-python/src/scan.rs | 1 + vortex-spatial/benches/binary_predicates.rs | 2 ++ vortex-spatial/benches/distance.rs | 2 ++ vortex-spatial/benches/make_line.rs | 1 + vortex-spatial/src/extension/point.rs | 1 + vortex-spatial/src/prune/distance.rs | 7 +++++++ vortex-spatial/src/prune/intersects.rs | 4 ++++ vortex-spatial/src/scalar_fn/collect.rs | 2 ++ vortex-spatial/src/scalar_fn/convex_hull.rs | 1 + vortex-spatial/src/scalar_fn/distance.rs | 2 ++ vortex-spatial/src/scalar_fn/envelope.rs | 2 ++ vortex-spatial/src/scalar_fn/length.rs | 2 ++ vortex-spatial/src/scalar_fn/make_line.rs | 2 ++ vortex-spatial/src/tests/point.rs | 2 ++ vortex-spatial/src/tests/rect.rs | 1 + vortex-spatial/src/tests/wkb.rs | 3 +++ .../src/scalar_fns/cosine_similarity.rs | 1 + vortex-tensor/src/scalar_fns/inner_product.rs | 1 + vortex-tensor/src/scalar_fns/l2_norm.rs | 1 + vortex-tensor/src/scalar_fns/l2_normalize.rs | 1 + .../arrays/synthetic/encodings/constant.rs | 1 + vortex-tui/src/browse/ui/layouts.rs | 1 + 185 files changed, 413 insertions(+), 12 deletions(-) diff --git a/encodings/alp/src/alp/ops.rs b/encodings/alp/src/alp/ops.rs index 58b5ee99360..6bd271c0939 100644 --- a/encodings/alp/src/alp/ops.rs +++ b/encodings/alp/src/alp/ops.rs @@ -17,6 +17,7 @@ use crate::match_each_alp_float_ptype; impl OperationsVTable for ALP { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, ALP>, index: usize, diff --git a/encodings/alp/src/alp_rd/ops.rs b/encodings/alp/src/alp_rd/ops.rs index 1433966a01e..1c585ba060e 100644 --- a/encodings/alp/src/alp_rd/ops.rs +++ b/encodings/alp/src/alp_rd/ops.rs @@ -16,6 +16,7 @@ use crate::ALPRDArraySlotsExt; impl OperationsVTable for ALPRD { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, ALPRD>, index: usize, diff --git a/encodings/bytebool/src/array.rs b/encodings/bytebool/src/array.rs index 02bb4fcb2b8..8ea8c755931 100644 --- a/encodings/bytebool/src/array.rs +++ b/encodings/bytebool/src/array.rs @@ -349,6 +349,7 @@ mod tests { }); #[test] + #[expect(deprecated)] fn test_validity_construction() { let v = vec![true, false]; let v_len = v.len(); diff --git a/encodings/datetime-parts/src/ops.rs b/encodings/datetime-parts/src/ops.rs index 0ee4f9e315c..14bd9088f6a 100644 --- a/encodings/datetime-parts/src/ops.rs +++ b/encodings/datetime-parts/src/ops.rs @@ -19,6 +19,7 @@ use crate::timestamp::TimestampParts; impl OperationsVTable for DateTimeParts { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, DateTimeParts>, index: usize, diff --git a/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs b/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs index e1d081ddaca..c2a9e5a0794 100644 --- a/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs +++ b/encodings/decimal-byte-parts/src/decimal_byte_parts/mod.rs @@ -291,6 +291,7 @@ fn to_canonical_decimal( impl OperationsVTable for DecimalByteParts { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, DecimalByteParts>, index: usize, @@ -336,6 +337,7 @@ mod tests { use crate::DecimalByteParts; #[test] + #[expect(deprecated)] fn test_scalar_at_decimal_parts() { let decimal_dtype = DecimalDType::new(8, 2); let dtype = DType::Decimal(decimal_dtype, Nullability::Nullable); diff --git a/encodings/fastlanes/src/bitpacking/array/bitpack_decompress.rs b/encodings/fastlanes/src/bitpacking/array/bitpack_decompress.rs index 0684aea5e6a..63659112b28 100644 --- a/encodings/fastlanes/src/bitpacking/array/bitpack_decompress.rs +++ b/encodings/fastlanes/src/bitpacking/array/bitpack_decompress.rs @@ -485,6 +485,7 @@ mod tests { /// This test ensures that the mask is properly appended to the range, not the builder. #[test] + #[expect(deprecated)] fn test_unpack_into_with_validity_mask() -> VortexResult<()> { // Create an array with some null values. let values = Buffer::from_iter([1u32, 0, 3, 4, 0]); @@ -596,6 +597,7 @@ mod tests { /// Test unpacking with nullability and validity masks. #[test] + #[expect(deprecated)] fn test_unpack_to_primitive_nullability() { // Test with null values at various positions. let values = Buffer::from_iter([100u32, 0, 200, 0, 300, 0, 400]); diff --git a/encodings/fastlanes/src/bitpacking/compute/take.rs b/encodings/fastlanes/src/bitpacking/compute/take.rs index 86e97623cf6..373a6825e30 100644 --- a/encodings/fastlanes/src/bitpacking/compute/take.rs +++ b/encodings/fastlanes/src/bitpacking/compute/take.rs @@ -238,6 +238,7 @@ mod test { #[test] #[cfg_attr(miri, ignore)] // This test is too slow on miri + #[expect(deprecated)] fn take_random_indices() { let mut ctx = SESSION.create_execution_ctx(); let num_patches: usize = 128; diff --git a/encodings/fastlanes/src/bitpacking/vtable/operations.rs b/encodings/fastlanes/src/bitpacking/vtable/operations.rs index 2816407ac03..2a152ed3981 100644 --- a/encodings/fastlanes/src/bitpacking/vtable/operations.rs +++ b/encodings/fastlanes/src/bitpacking/vtable/operations.rs @@ -237,6 +237,7 @@ mod test { } #[test] + #[expect(deprecated)] fn scalar_at_invalid_patches() { let packed_array = BitPacked::try_new( BufferHandle::new_host(ByteBuffer::copy_from_aligned( @@ -270,6 +271,7 @@ mod test { } #[test] + #[expect(deprecated)] fn scalar_at() { let mut ctx = SESSION.create_execution_ctx(); let values = (0u32..257).collect::>(); diff --git a/encodings/fastlanes/src/delta/array/delta_compress.rs b/encodings/fastlanes/src/delta/array/delta_compress.rs index c2ef38ceb79..0d68280e374 100644 --- a/encodings/fastlanes/src/delta/array/delta_compress.rs +++ b/encodings/fastlanes/src/delta/array/delta_compress.rs @@ -127,6 +127,7 @@ where #[cfg(test)] mod tests { + #![expect(deprecated)] use std::iter; use std::sync::LazyLock; diff --git a/encodings/fastlanes/src/delta/vtable/operations.rs b/encodings/fastlanes/src/delta/vtable/operations.rs index b37760621ea..616d094974a 100644 --- a/encodings/fastlanes/src/delta/vtable/operations.rs +++ b/encodings/fastlanes/src/delta/vtable/operations.rs @@ -13,6 +13,7 @@ use super::Delta; impl OperationsVTable for Delta { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Delta>, index: usize, @@ -221,6 +222,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated)] fn test_scalar_at_non_jagged_array_oob() { let delta = da(&(0u32..2048).collect()).into_array(); delta @@ -237,6 +239,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated)] fn test_scalar_at_jagged_array_oob() { let delta = da(&(0u32..2000).collect()).into_array(); delta diff --git a/encodings/fastlanes/src/for/array/for_compress.rs b/encodings/fastlanes/src/for/array/for_compress.rs index 62c23b7ba21..61ba035ad58 100644 --- a/encodings/fastlanes/src/for/array/for_compress.rs +++ b/encodings/fastlanes/src/for/array/for_compress.rs @@ -104,6 +104,7 @@ mod test { } #[test] + #[expect(deprecated)] fn test_zeros() { let mut ctx = SESSION.create_execution_ctx(); let array = PrimitiveArray::new(buffer![0i32; 100], Validity::NonNullable); @@ -153,6 +154,7 @@ mod test { } #[test] + #[expect(deprecated)] fn test_overflow() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let array = PrimitiveArray::from_iter(i8::MIN..=i8::MAX); diff --git a/encodings/fastlanes/src/for/vtable/operations.rs b/encodings/fastlanes/src/for/vtable/operations.rs index 361020824d5..383a86ce696 100644 --- a/encodings/fastlanes/src/for/vtable/operations.rs +++ b/encodings/fastlanes/src/for/vtable/operations.rs @@ -15,6 +15,7 @@ use crate::r#for::array::FoRArraySlotsExt; impl OperationsVTable for FoR { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, FoR>, index: usize, diff --git a/encodings/fastlanes/src/rle/array/mod.rs b/encodings/fastlanes/src/rle/array/mod.rs index 2805b999a4f..420f7dcd2e9 100644 --- a/encodings/fastlanes/src/rle/array/mod.rs +++ b/encodings/fastlanes/src/rle/array/mod.rs @@ -93,6 +93,7 @@ pub trait RLEArrayExt: RLEArraySlotsExt { clippy::expect_used, reason = "expect is safe here as scalar_at returns a valid primitive" )] + #[expect(deprecated)] fn values_idx_offset(&self, chunk_idx: usize, ctx: &mut ExecutionCtx) -> usize { self.values_idx_offsets() .execute_scalar(chunk_idx, ctx) @@ -164,6 +165,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_try_new_with_validity() { let values = PrimitiveArray::from_iter([10u32, 20]).into_array(); let values_idx_offsets = PrimitiveArray::from_iter([0u64]).into_array(); diff --git a/encodings/fastlanes/src/rle/vtable/operations.rs b/encodings/fastlanes/src/rle/vtable/operations.rs index ba6a624f1f5..f4fe4df2bad 100644 --- a/encodings/fastlanes/src/rle/vtable/operations.rs +++ b/encodings/fastlanes/src/rle/vtable/operations.rs @@ -16,6 +16,7 @@ use crate::rle::RLEArraySlotsExt; impl OperationsVTable for RLE { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, RLE>, index: usize, @@ -187,6 +188,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_scalar_at_multiple_chunks() { let mut ctx = SESSION.create_execution_ctx(); // Test accessing elements around chunk boundaries @@ -214,6 +216,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated)] fn test_scalar_at_out_of_bounds() { let array = fixture::rle_array(); array @@ -223,6 +226,7 @@ mod tests { #[test] #[should_panic] + #[expect(deprecated)] fn test_scalar_at_slice_out_of_bounds() { let array = fixture::rle_array().slice(0..1).unwrap(); array diff --git a/encodings/fastlanes/src/transposed_bool.rs b/encodings/fastlanes/src/transposed_bool.rs index 92da3b3b79c..338af911bc6 100644 --- a/encodings/fastlanes/src/transposed_bool.rs +++ b/encodings/fastlanes/src/transposed_bool.rs @@ -258,6 +258,7 @@ impl VTable for TransposedBool { impl OperationsVTable for TransposedBool { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, TransposedBool>, index: usize, @@ -327,6 +328,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn slice_stays_lazy_and_translates_scalars() -> VortexResult<()> { let expected = test_bits(); let array = TransposedBool::try_new(transposed_bool_array(expected.clone()))?; diff --git a/encodings/fsst/src/array.rs b/encodings/fsst/src/array.rs index 86b8fd0c34d..835bd05db93 100644 --- a/encodings/fsst/src/array.rs +++ b/encodings/fsst/src/array.rs @@ -791,6 +791,7 @@ impl FSSTData { /// Validate using the decomposed components (codes bytes + offsets + nullability). #[expect(clippy::too_many_arguments)] + #[expect(deprecated)] fn validate_parts( symbols: &[Symbol], symbol_lengths: &[u8], diff --git a/encodings/fsst/src/ops.rs b/encodings/fsst/src/ops.rs index 03560b2c8b8..c6c265da713 100644 --- a/encodings/fsst/src/ops.rs +++ b/encodings/fsst/src/ops.rs @@ -16,6 +16,7 @@ use crate::FSSTArrayExt; impl OperationsVTable for FSST { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, FSST>, index: usize, diff --git a/encodings/onpair/src/decode.rs b/encodings/onpair/src/decode.rs index 7aac8c3963f..15c9dc1dc3e 100644 --- a/encodings/onpair/src/decode.rs +++ b/encodings/onpair/src/decode.rs @@ -32,6 +32,7 @@ pub(crate) fn collect_widened( .into_buffer::()) } +#[expect(deprecated)] pub(crate) fn code_boundary_at( codes_offsets: &ArrayRef, index: usize, diff --git a/encodings/onpair/src/ops.rs b/encodings/onpair/src/ops.rs index 082bc55e199..ca9a22f75d5 100644 --- a/encodings/onpair/src/ops.rs +++ b/encodings/onpair/src/ops.rs @@ -20,6 +20,7 @@ use crate::decode::collect_widened; impl OperationsVTable for OnPair { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, OnPair>, index: usize, diff --git a/encodings/onpair/src/tests.rs b/encodings/onpair/src/tests.rs index 7dac64befc2..cd1adcea4e0 100644 --- a/encodings/onpair/src/tests.rs +++ b/encodings/onpair/src/tests.rs @@ -108,6 +108,7 @@ fn test_onpair_rejects_100k_token_dictionary() -> vortex_error::VortexResult<()> /// decodes or searches through it, including on derived (sliced) arrays. #[cfg_attr(miri, ignore)] #[test] +#[expect(deprecated)] fn test_corrupt_dictionary_rejected_on_first_use() -> vortex_error::VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let arr = compress_onpair(&sample_input().into_array(), &mut ctx)?; @@ -267,6 +268,7 @@ fn test_onpair_nullable_canonicalize() -> vortex_error::VortexResult<()> { #[cfg_attr(miri, ignore)] #[test] +#[expect(deprecated)] fn test_onpair_scalar_at() -> vortex_error::VortexResult<()> { let input = sample_input(); let mut ctx = SESSION.create_execution_ctx(); @@ -285,6 +287,7 @@ fn test_onpair_scalar_at() -> vortex_error::VortexResult<()> { /// relative to the slice. #[cfg_attr(miri, ignore)] #[test] +#[expect(deprecated)] fn test_onpair_scalar_at_window() -> vortex_error::VortexResult<()> { let n = 2_000usize; let strings: Vec = (0..n) diff --git a/encodings/parquet-variant/src/array.rs b/encodings/parquet-variant/src/array.rs index 5d364cae0ef..c67dd26d1a3 100644 --- a/encodings/parquet-variant/src/array.rs +++ b/encodings/parquet-variant/src/array.rs @@ -809,6 +809,7 @@ mod tests { /// canonicalization performs: an object-shredded Parquet variant, once canonicalized and then /// rebuilt, must produce the same per-row values as the original. #[test] + #[expect(deprecated)] fn parquet_typed_value_inverse_roundtrips_object_shredding() -> VortexResult<()> { // Shred `$.a` as Int32 over conforming, non-conforming, and missing-field rows. let json: ArrowArrayRef = Arc::new(StringArray::from(vec![ diff --git a/encodings/parquet-variant/src/arrow.rs b/encodings/parquet-variant/src/arrow.rs index e1c03d704dc..843c40654ec 100644 --- a/encodings/parquet-variant/src/arrow.rs +++ b/encodings/parquet-variant/src/arrow.rs @@ -353,6 +353,7 @@ mod tests { } } + #[expect(deprecated)] fn assert_variant_scalars_eq( actual: &vortex_array::ArrayRef, expected: &vortex_array::ArrayRef, diff --git a/encodings/parquet-variant/src/json_to_variant_tests.rs b/encodings/parquet-variant/src/json_to_variant_tests.rs index 5bc09a426f7..312b0098fec 100644 --- a/encodings/parquet-variant/src/json_to_variant_tests.rs +++ b/encodings/parquet-variant/src/json_to_variant_tests.rs @@ -65,6 +65,7 @@ fn execute_json_to_variant(input: ArrayRef, shredding: ShreddingSpec) -> VortexR .execute::(&mut SESSION.create_execution_ctx()) } +#[expect(deprecated)] fn assert_variant_i64_rows(array: &ArrayRef, expected: &[Option]) -> VortexResult<()> { assert_eq!(array.len(), expected.len()); let mut ctx = SESSION.create_execution_ctx(); @@ -97,6 +98,7 @@ fn rejects_bare_utf8_input() { } #[test] +#[expect(deprecated)] fn converts_json_extension_rows() -> VortexResult<()> { let input = json_input( VarBinViewArray::from_iter_str([r#"{"a": 1}"#, "2", r#"{"a": 3}"#]).into_array(), @@ -158,6 +160,7 @@ fn dict_encoded_input_converts_each_row() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn null_rows_stay_null_and_json_null_becomes_variant_null() -> VortexResult<()> { let input = json_input( VarBinViewArray::from_iter_nullable_str([Some("1"), None, Some("null")]).into_array(), @@ -186,6 +189,7 @@ fn invalid_json_errors() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn shredding_produces_typed_value_child() -> VortexResult<()> { let input = json_input( VarBinViewArray::from_iter_str([ @@ -275,6 +279,7 @@ fn shredding_preserves_null_rows() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn shredding_root_path_shreds_top_level_values() -> VortexResult<()> { let input = json_input(VarBinViewArray::from_iter_str(["1", "2", r#""not-a-number""#]).into_array())?; diff --git a/encodings/parquet-variant/src/kernel.rs b/encodings/parquet-variant/src/kernel.rs index 90aa9eba548..e4f398c7ab2 100644 --- a/encodings/parquet-variant/src/kernel.rs +++ b/encodings/parquet-variant/src/kernel.rs @@ -544,9 +544,13 @@ mod tests { let mut ctx = SESSION.create_execution_ctx(); for (actual_idx, expected_idx) in expected_rows.into_iter().enumerate() { + #[expect(deprecated)] + let actual_scalar = actual.execute_scalar(actual_idx, &mut ctx)?; + #[expect(deprecated)] + let expected_scalar = expected.execute_scalar(expected_idx, &mut ctx)?; assert_eq!( - actual.execute_scalar(actual_idx, &mut ctx)?, - expected.execute_scalar(expected_idx, &mut ctx)?, + actual_scalar, + expected_scalar, "row {actual_idx} should match source row {expected_idx}", ); } @@ -561,8 +565,10 @@ mod tests { let mut ctx = SESSION.create_execution_ctx(); for (idx, is_null) in expected.into_iter().enumerate() { + #[expect(deprecated)] + let scalar = array.execute_scalar(idx, &mut ctx)?; assert_eq!( - array.execute_scalar(idx, &mut ctx)?.is_null(), + scalar.is_null(), is_null, "row {idx} nullness mismatch", ); @@ -673,6 +679,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_variant_get_unshredded_field_as_variant() -> VortexResult<()> { let arr = make_unshredded_json_array(vec![ Some(r#"{"a": "ok"}"#), @@ -900,6 +907,7 @@ mod tests { Ok(VariantArray::try_new(raw_core, Some(shredded))?.into_array()) } + #[expect(deprecated)] fn assert_variant_i32_scalars(array: &ArrayRef, expected: &[Option]) -> VortexResult<()> { assert_eq!(array.len(), expected.len()); let mut ctx = SESSION.create_execution_ctx(); @@ -921,6 +929,7 @@ mod tests { Ok(()) } + #[expect(deprecated)] fn assert_variant_object_a_b( array: &ArrayRef, expected_a: &[Option], diff --git a/encodings/parquet-variant/src/operations.rs b/encodings/parquet-variant/src/operations.rs index eab80bf63e1..fe6e2100c12 100644 --- a/encodings/parquet-variant/src/operations.rs +++ b/encodings/parquet-variant/src/operations.rs @@ -38,6 +38,7 @@ impl OperationsVTable for ParquetVariant { /// For valid data, a row with both `value` and struct `typed_value` is a partially /// shredded object: recursively reconstruct shredded fields and merge them with the /// raw-only fields from `value`. + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, ParquetVariant>, index: usize, @@ -69,6 +70,7 @@ impl OperationsVTable for ParquetVariant { } } +#[expect(deprecated)] fn scalar_from_variant_storage( metadata: &[u8], value: Option<&ArrayRef>, @@ -94,6 +96,7 @@ fn scalar_from_variant_storage( Ok(Scalar::null(DType::Null)) } +#[expect(deprecated)] fn scalar_from_typed_value_array( metadata: &[u8], value: Option<&ArrayRef>, @@ -418,6 +421,7 @@ mod tests { Arc::new(builder.finish()) } + #[expect(deprecated)] fn assert_scalar_at_matches_arrow_try_value( arrow_variant: &ArrowVariantArray, rows: impl IntoIterator, @@ -440,6 +444,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_from_arrow_variant_nullable_validity() -> VortexResult<()> { let mut builder = VariantArrayBuilder::new(3); builder.append_variant(PqVariant::from(42i32)); @@ -481,6 +486,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_outer_null_and_variant_null_are_distinct() -> VortexResult<()> { let mut builder = VariantArrayBuilder::new(3); builder.append_variant(PqVariant::Null); @@ -519,6 +525,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_from_arrow_variant_all_nulls() -> VortexResult<()> { let mut builder = VariantArrayBuilder::new(2); builder.append_variant(PqVariant::from(1i32)); @@ -555,6 +562,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_from_arrow_variant_non_nullable() -> VortexResult<()> { let mut builder = VariantArrayBuilder::new(2); builder.append_variant(PqVariant::from(1i32)); @@ -646,6 +654,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_scalar_at_recursive_shredded_list() -> VortexResult<()> { // Spec basis: for arrays, "value must be null" when the value is an array, and array // elements cannot be missing. @@ -726,6 +735,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_scalar_at_partially_shredded_object_merges_fields() -> VortexResult<()> { // Spec basis: non-null `value` + non-null `typed_value` means a "partially shredded // object", so reconstruction must merge the shredded object with the fallback object. diff --git a/encodings/pco/src/array.rs b/encodings/pco/src/array.rs index 97a17fe6a3c..2441cd435ad 100644 --- a/encodings/pco/src/array.rs +++ b/encodings/pco/src/array.rs @@ -780,6 +780,7 @@ impl ValidityVTable for Pco { impl OperationsVTable for Pco { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Pco>, index: usize, diff --git a/encodings/runend/src/array.rs b/encodings/runend/src/array.rs index 3a0b10c5c4a..77e287a1564 100644 --- a/encodings/runend/src/array.rs +++ b/encodings/runend/src/array.rs @@ -308,6 +308,7 @@ impl RunEnd { } impl RunEndData { + #[expect(deprecated)] fn logical_len_from_ends(ends: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { if ends.is_empty() { Ok(0) @@ -318,6 +319,7 @@ impl RunEndData { /// Validate that `ends` and `values` form a well-formed run-end array covering /// `offset..offset + length`. + #[expect(deprecated)] pub fn validate_parts( ends: &ArrayRef, values: &ArrayRef, diff --git a/encodings/runend/src/compute/cast.rs b/encodings/runend/src/compute/cast.rs index aa2dc5eb406..ccbd27c8cfc 100644 --- a/encodings/runend/src/compute/cast.rs +++ b/encodings/runend/src/compute/cast.rs @@ -60,6 +60,7 @@ mod tests { }); #[test] + #[expect(deprecated)] fn test_cast_runend_i32_to_i64() { let mut ctx = SESSION.create_execution_ctx(); let runend = RunEnd::try_new( diff --git a/encodings/runend/src/kernel.rs b/encodings/runend/src/kernel.rs index 2eed964e3a9..ff731f64078 100644 --- a/encodings/runend/src/kernel.rs +++ b/encodings/runend/src/kernel.rs @@ -57,6 +57,7 @@ impl ExecuteParentKernel for RunEndSliceKernel { } } +#[expect(deprecated)] fn slice( array: ArrayView<'_, RunEnd>, range: Range, diff --git a/encodings/runend/src/ops.rs b/encodings/runend/src/ops.rs index 46949d84e1b..1d99f5cb777 100644 --- a/encodings/runend/src/ops.rs +++ b/encodings/runend/src/ops.rs @@ -20,6 +20,7 @@ use crate::array::RunEndArraySlotsExt; impl OperationsVTable for RunEnd { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, RunEnd>, index: usize, @@ -186,6 +187,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn ree_scalar_at_end() { let mut ctx = SESSION.create_execution_ctx(); let scalar = RunEnd::encode( diff --git a/encodings/sequence/src/array.rs b/encodings/sequence/src/array.rs index e61eb54e4eb..c82e728feda 100644 --- a/encodings/sequence/src/array.rs +++ b/encodings/sequence/src/array.rs @@ -589,6 +589,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_sequence_scalar_at() { let scalar = Sequence::try_new_typed(2i64, 3, Nullability::NonNullable, 4) .unwrap() @@ -729,6 +730,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn descending_step_unsigned_output() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let array = Sequence::try_new( @@ -777,6 +779,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn values_past_i64_max() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let step = 1u64 << 62; diff --git a/encodings/sequence/src/compute/cast.rs b/encodings/sequence/src/compute/cast.rs index fa5d3a7661d..aa16301081d 100644 --- a/encodings/sequence/src/compute/cast.rs +++ b/encodings/sequence/src/compute/cast.rs @@ -156,6 +156,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_cast_sequence_narrows_to_output_dtype() -> VortexResult<()> { let casted = Sequence::try_new_typed(100i32, -10i32, Nullability::NonNullable, 5)? .into_array() diff --git a/encodings/sequence/src/model_tests.rs b/encodings/sequence/src/model_tests.rs index eb93c4199c3..a6aff56b1a2 100644 --- a/encodings/sequence/src/model_tests.rs +++ b/encodings/sequence/src/model_tests.rs @@ -122,6 +122,7 @@ fn constant(value: i128, ptype: PType, len: usize) -> VortexResult { } #[test] +#[expect(deprecated)] fn sequence_kernels_match_exact_model() -> VortexResult<()> { let session = vortex_array::array_session(); crate::initialize(&session); diff --git a/encodings/sparse/src/compute/between.rs b/encodings/sparse/src/compute/between.rs index 46fcd7f1e59..a5a914d67e0 100644 --- a/encodings/sparse/src/compute/between.rs +++ b/encodings/sparse/src/compute/between.rs @@ -23,6 +23,7 @@ use crate::SparseExt as _; /// /// Declines (falls back to canonical) unless both bounds are constants. impl BetweenKernel for Sparse { + #[expect(deprecated)] fn between( array: ArrayView<'_, Self>, lower: &ArrayRef, diff --git a/encodings/sparse/src/compute/is_constant.rs b/encodings/sparse/src/compute/is_constant.rs index eee8683df0a..d2868be77dd 100644 --- a/encodings/sparse/src/compute/is_constant.rs +++ b/encodings/sparse/src/compute/is_constant.rs @@ -26,6 +26,7 @@ use crate::SparseExt as _; pub(crate) struct SparseIsConstantKernel; impl DynAggregateKernel for SparseIsConstantKernel { + #[expect(deprecated)] fn aggregate( &self, aggregate_fn: &AggregateFnRef, diff --git a/encodings/sparse/src/lib.rs b/encodings/sparse/src/lib.rs index 70f993c10ff..5b107832868 100644 --- a/encodings/sparse/src/lib.rs +++ b/encodings/sparse/src/lib.rs @@ -771,6 +771,7 @@ mod test { } #[test] + #[expect(deprecated)] pub fn test_scalar_at() { let array = sparse_array(nullable_fill()); @@ -796,6 +797,7 @@ mod test { #[test] #[should_panic(expected = "out of bounds")] + #[expect(deprecated)] fn test_scalar_at_oob() { let array = sparse_array(nullable_fill()); array @@ -804,6 +806,7 @@ mod test { } #[test] + #[expect(deprecated)] pub fn test_scalar_at_again() { let arr = Sparse::try_new( ConstantArray::new(10u32, 1).into_array(), @@ -833,6 +836,7 @@ mod test { } #[test] + #[expect(deprecated)] pub fn scalar_at_sliced() { let sliced = sparse_array(nullable_fill()).slice(2..7).unwrap(); assert_eq!( @@ -886,6 +890,7 @@ mod test { } #[test] + #[expect(deprecated)] pub fn scalar_at_sliced_twice() { let sliced_once = sparse_array(nullable_fill()).slice(1..8).unwrap(); assert_eq!( diff --git a/encodings/zigzag/src/array.rs b/encodings/zigzag/src/array.rs index 09076fcbbdf..1aa6cfb07fe 100644 --- a/encodings/zigzag/src/array.rs +++ b/encodings/zigzag/src/array.rs @@ -233,6 +233,7 @@ impl Default for ZigZagData { impl OperationsVTable for ZigZag { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, ZigZag>, index: usize, @@ -276,6 +277,7 @@ mod test { use crate::zigzag_encode; #[test] + #[expect(deprecated)] fn test_compute_statistics() -> VortexResult<()> { let mut ctx = array_session().create_execution_ctx(); let array = buffer![1i32, -5i32, 2, 3, 4, 5, 6, 7, 8, 9, 10] diff --git a/encodings/zigzag/src/compute/mod.rs b/encodings/zigzag/src/compute/mod.rs index 354593e284a..75e91b5ae2c 100644 --- a/encodings/zigzag/src/compute/mod.rs +++ b/encodings/zigzag/src/compute/mod.rs @@ -92,6 +92,7 @@ mod tests { }); #[test] + #[expect(deprecated)] pub fn nullable_scalar_at() -> VortexResult<()> { let zigzag = zigzag_encode( PrimitiveArray::new(buffer![-189, -160, 1], Validity::AllValid).as_view(), diff --git a/encodings/zstd/src/array.rs b/encodings/zstd/src/array.rs index dafe8e820a9..f3f84ff3acf 100644 --- a/encodings/zstd/src/array.rs +++ b/encodings/zstd/src/array.rs @@ -1590,6 +1590,7 @@ impl ValidityVTable for Zstd { impl OperationsVTable for Zstd { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Zstd>, index: usize, diff --git a/encodings/zstd/src/test.rs b/encodings/zstd/src/test.rs index fafbfcaf159..2d3f31a5782 100644 --- a/encodings/zstd/src/test.rs +++ b/encodings/zstd/src/test.rs @@ -79,6 +79,7 @@ fn test_zstd_empty() { } #[test] +#[expect(deprecated)] fn test_zstd_with_validity_and_multi_frame() { let mut ctx = array_session().create_execution_ctx(); let data: Vec = (0..200).collect(); diff --git a/encodings/zstd/src/zstd_buffers.rs b/encodings/zstd/src/zstd_buffers.rs index 51d338777ca..883f435f6cd 100644 --- a/encodings/zstd/src/zstd_buffers.rs +++ b/encodings/zstd/src/zstd_buffers.rs @@ -522,6 +522,7 @@ impl VTable for ZstdBuffers { impl OperationsVTable for ZstdBuffers { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, ZstdBuffers>, index: usize, @@ -688,6 +689,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_validity_delegates_for_nullable_input() -> VortexResult<()> { let input = make_nullable_primitive_array(); let compressed = ZstdBuffers::compress(&input, 3, &array_session())?.into_array(); diff --git a/fuzz/src/array/compare.rs b/fuzz/src/array/compare.rs index 03ce50a7170..6fe2b1a1051 100644 --- a/fuzz/src/array/compare.rs +++ b/fuzz/src/array/compare.rs @@ -23,6 +23,7 @@ use vortex_buffer::ByteBuffer; use vortex_error::VortexExpect; use vortex_error::vortex_panic; +#[expect(deprecated)] pub fn compare_canonical_array( array: &ArrayRef, value: &Scalar, diff --git a/fuzz/src/array/fill_null.rs b/fuzz/src/array/fill_null.rs index fd8d8ec88c3..2e6ee1f9d9a 100644 --- a/fuzz/src/array/fill_null.rs +++ b/fuzz/src/array/fill_null.rs @@ -198,6 +198,7 @@ fn fill_decimal_array( }) } +#[expect(deprecated)] fn fill_varbinview_array( array: VarBinViewArray, fill_value: &Scalar, diff --git a/fuzz/src/array/filter.rs b/fuzz/src/array/filter.rs index 5b90d1cecc8..bad5f860a1e 100644 --- a/fuzz/src/array/filter.rs +++ b/fuzz/src/array/filter.rs @@ -22,6 +22,7 @@ use vortex_error::VortexResult; use crate::array::take_canonical_array_non_nullable_indices; +#[expect(deprecated)] pub fn filter_canonical_array( array: &ArrayRef, filter: &[bool], diff --git a/fuzz/src/array/mask.rs b/fuzz/src/array/mask.rs index e59c2f4fed9..1bfe9829132 100644 --- a/fuzz/src/array/mask.rs +++ b/fuzz/src/array/mask.rs @@ -63,6 +63,7 @@ pub fn mask_validity(validity: &Validity, mask: &Mask, ctx: &mut ExecutionCtx) - /// Apply mask on the canonical form of the array to get a consistent baseline. /// This implementation manually applies the mask to each canonical type /// without using the mask_fn method, to serve as an independent baseline for testing. +#[expect(deprecated)] pub fn mask_canonical_array( canonical: Canonical, mask: &Mask, @@ -198,6 +199,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_mask_null_array() { let mut ctx = array_session().create_execution_ctx(); let array = NullArray::new(5); @@ -279,6 +281,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_mask_list_array() { let mut ctx = array_session().create_execution_ctx(); let elements = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5, 6]).into_array(); @@ -300,6 +303,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_mask_fixed_size_list_array() { let mut ctx = array_session().create_execution_ctx(); let elements = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5, 6]).into_array(); @@ -317,6 +321,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_mask_struct_array() { let mut ctx = array_session().create_execution_ctx(); let field1 = PrimitiveArray::from_iter([1i32, 2, 3]).into_array(); diff --git a/fuzz/src/array/mod.rs b/fuzz/src/array/mod.rs index e513c5daf81..1122a96c030 100644 --- a/fuzz/src/array/mod.rs +++ b/fuzz/src/array/mod.rs @@ -175,6 +175,7 @@ impl ExpectedValue { } impl<'a> Arbitrary<'a> for FuzzArrayAction { + #[expect(deprecated)] fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { let array = ArbitraryArray::arbitrary_with_config( u, @@ -591,6 +592,7 @@ pub fn compress_array( /// - `Ok(false)` - reject from corpus /// - `Err(_)` - a bug was found #[expect(clippy::result_large_err)] +#[expect(deprecated)] pub fn run_fuzz_action(fuzz_action: FuzzArrayAction) -> VortexFuzzResult { let FuzzArrayAction { array, actions } = fuzz_action; let mut current_array = array; @@ -739,6 +741,7 @@ fn assert_search_sorted( /// Uses `all_non_distinct` for an efficient buffer-level comparison on the happy path. /// Falls back to element-wise scalar comparison only on mismatch to produce a detailed error. #[expect(clippy::result_large_err)] +#[expect(deprecated)] pub fn assert_array_eq( lhs: &ArrayRef, rhs: &ArrayRef, diff --git a/fuzz/src/array/scalar_at.rs b/fuzz/src/array/scalar_at.rs index a391822f67e..de0c6f0b073 100644 --- a/fuzz/src/array/scalar_at.rs +++ b/fuzz/src/array/scalar_at.rs @@ -25,6 +25,7 @@ use vortex_error::VortexResult; /// Baseline implementation of scalar_at that works on canonical arrays. /// This implementation manually extracts the scalar value from each canonical type /// without using the scalar_at method, to serve as an independent baseline for testing. +#[expect(deprecated)] pub fn scalar_at_canonical_array( canonical: Canonical, index: usize, diff --git a/fuzz/src/array/search_sorted.rs b/fuzz/src/array/search_sorted.rs index 6eb0d63e93a..23efe253b15 100644 --- a/fuzz/src/array/search_sorted.rs +++ b/fuzz/src/array/search_sorted.rs @@ -59,6 +59,7 @@ impl IndexOrd> for SearchPrimitiveSlice { } } +#[expect(deprecated)] pub fn search_sorted_canonical_array( array: &ArrayRef, scalar: &Scalar, diff --git a/fuzz/src/array/slice.rs b/fuzz/src/array/slice.rs index e121337e5d3..2b977181cf1 100644 --- a/fuzz/src/array/slice.rs +++ b/fuzz/src/array/slice.rs @@ -23,6 +23,7 @@ use vortex_array::match_each_native_ptype; use vortex_array::validity::Validity; use vortex_error::VortexResult; +#[expect(deprecated)] pub fn slice_canonical_array( array: &ArrayRef, start: usize, diff --git a/fuzz/src/array/sort.rs b/fuzz/src/array/sort.rs index b0af8638898..b5772c9acd6 100644 --- a/fuzz/src/array/sort.rs +++ b/fuzz/src/array/sort.rs @@ -20,6 +20,7 @@ use vortex_error::VortexResult; use crate::array::take_canonical_array_non_nullable_indices; +#[expect(deprecated)] pub fn sort_canonical_array(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { match array.dtype() { DType::Bool(_) => { diff --git a/fuzz/src/array/take.rs b/fuzz/src/array/take.rs index 460cdf1593c..def3e627b8f 100644 --- a/fuzz/src/array/take.rs +++ b/fuzz/src/array/take.rs @@ -40,6 +40,7 @@ pub fn take_canonical_array_non_nullable_indices( ) } +#[expect(deprecated)] pub fn take_canonical_array( array: &ArrayRef, indices: &[Option], diff --git a/fuzz/src/gpu/mod.rs b/fuzz/src/gpu/mod.rs index 92691949cdf..877aee80e88 100644 --- a/fuzz/src/gpu/mod.rs +++ b/fuzz/src/gpu/mod.rs @@ -96,6 +96,7 @@ fn arbitrary_gpu_primitive_dtype(u: &mut Unstructured) -> Result VortexFuzzResult { use vortex_cuda::CanonicalCudaExt; use vortex_cuda::CudaSession; diff --git a/vortex-array/benches/scalar_at_struct.rs b/vortex-array/benches/scalar_at_struct.rs index 2564120f12e..12546e1f8b4 100644 --- a/vortex-array/benches/scalar_at_struct.rs +++ b/vortex-array/benches/scalar_at_struct.rs @@ -32,6 +32,7 @@ const NUM_ACCESSES: usize = 100; static SESSION: LazyLock = LazyLock::new(array_session); #[divan::bench] +#[expect(deprecated)] fn execute_scalar_struct_simple(bencher: Bencher) { let mut rng = StdRng::seed_from_u64(0); let range = Uniform::new(0i64, 100_000_000).unwrap(); @@ -65,6 +66,7 @@ fn execute_scalar_struct_simple(bencher: Bencher) { } #[divan::bench] +#[expect(deprecated)] fn execute_scalar_struct_wide(bencher: Bencher) { let mut rng = StdRng::seed_from_u64(0); let range = Uniform::new(0i64, 100_000_000).unwrap(); diff --git a/vortex-array/src/aggregate_fn/fns/all_non_distinct/tests.rs b/vortex-array/src/aggregate_fn/fns/all_non_distinct/tests.rs index ab513670c18..2b29ad5fd8d 100644 --- a/vortex-array/src/aggregate_fn/fns/all_non_distinct/tests.rs +++ b/vortex-array/src/aggregate_fn/fns/all_non_distinct/tests.rs @@ -29,6 +29,7 @@ use crate::validity::Validity; /// Baseline oracle: compare two arrays element-wise using `execute_scalar`. /// Returns true iff every position has the same scalar (null == null is true). +#[expect(deprecated)] fn scalar_baseline(a: &ArrayRef, b: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult { assert_eq!(a.len(), b.len()); for i in 0..a.len() { diff --git a/vortex-array/src/aggregate_fn/fns/all_non_distinct/variant.rs b/vortex-array/src/aggregate_fn/fns/all_non_distinct/variant.rs index 1576b8c3d6a..04476d3b72a 100644 --- a/vortex-array/src/aggregate_fn/fns/all_non_distinct/variant.rs +++ b/vortex-array/src/aggregate_fn/fns/all_non_distinct/variant.rs @@ -15,6 +15,7 @@ use crate::arrays::VariantArray; /// aggregate kernel that intercepts the comparison before it reaches this fallback. /// /// [`all_non_distinct`]: super::all_non_distinct +#[expect(deprecated)] pub(super) fn check_variant_identical( lhs: &VariantArray, rhs: &VariantArray, diff --git a/vortex-array/src/aggregate_fn/fns/first/mod.rs b/vortex-array/src/aggregate_fn/fns/first/mod.rs index c41e2575057..6ba7fe14138 100644 --- a/vortex-array/src/aggregate_fn/fns/first/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/first/mod.rs @@ -92,6 +92,7 @@ impl AggregateFnVTable for First { partial.value.is_some() } + #[expect(deprecated)] fn try_accumulate( &self, partial: &mut Self::Partial, diff --git a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs index 96017c3ed92..b0907a2f7f4 100644 --- a/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/is_constant/mod.rs @@ -187,6 +187,7 @@ impl IsConstant { /// Kernels that compute `is_constant` by delegating to child arrays can call this /// to package the boolean result into the partial struct format expected by the /// accumulator, avoiding duplicated boilerplate. + #[expect(deprecated)] pub fn make_partial( batch: &ArrayRef, is_constant: bool, @@ -354,6 +355,7 @@ impl AggregateFnVTable for IsConstant { !partial.is_constant } + #[expect(deprecated)] fn accumulate( &self, partial: &mut Self::Partial, diff --git a/vortex-array/src/aggregate_fn/fns/is_sorted/mod.rs b/vortex-array/src/aggregate_fn/fns/is_sorted/mod.rs index 99a7b99128a..b61be19ddac 100644 --- a/vortex-array/src/aggregate_fn/fns/is_sorted/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/is_sorted/mod.rs @@ -70,6 +70,7 @@ pub fn is_strict_sorted(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResul is_sorted_impl(array, true, ctx) } +#[expect(deprecated)] fn is_sorted_impl(array: &ArrayRef, strict: bool, ctx: &mut ExecutionCtx) -> VortexResult { let stat = if strict { Stat::IsStrictSorted @@ -172,6 +173,7 @@ impl IsSorted { /// Kernels that compute `is_sorted` by delegating to child arrays can call this /// to package the boolean result into the partial struct format expected by the /// accumulator, avoiding duplicated boilerplate. + #[expect(deprecated)] pub fn make_partial( batch: &ArrayRef, is_sorted: bool, @@ -401,6 +403,7 @@ impl AggregateFnVTable for IsSorted { !partial.is_sorted } + #[expect(deprecated)] fn accumulate( &self, partial: &mut Self::Partial, diff --git a/vortex-array/src/aggregate_fn/fns/last/mod.rs b/vortex-array/src/aggregate_fn/fns/last/mod.rs index 63ee54a6efa..73a1ca6d8e4 100644 --- a/vortex-array/src/aggregate_fn/fns/last/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/last/mod.rs @@ -93,6 +93,7 @@ impl AggregateFnVTable for Last { false } + #[expect(deprecated)] fn try_accumulate( &self, partial: &mut Self::Partial, diff --git a/vortex-array/src/aggregate_fn/fns/mean/mod.rs b/vortex-array/src/aggregate_fn/fns/mean/mod.rs index a3a0f77cb45..49a9b9d8d28 100644 --- a/vortex-array/src/aggregate_fn/fns/mean/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/mean/mod.rs @@ -435,6 +435,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn mean_grouped_finalize() -> VortexResult<()> { let cases = mean_nan_null(); let elements = PrimitiveArray::from_option_iter( diff --git a/vortex-array/src/aggregate_fn/fns/sum/grouped.rs b/vortex-array/src/aggregate_fn/fns/sum/grouped.rs index c44feb44823..fade8cd0510 100644 --- a/vortex-array/src/aggregate_fn/fns/sum/grouped.rs +++ b/vortex-array/src/aggregate_fn/fns/sum/grouped.rs @@ -323,6 +323,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn listview_float_nan_and_inf() -> VortexResult<()> { let elements = PrimitiveArray::new( buffer![1.0f64, f64::NAN, 2.0, f64::INFINITY, f64::NEG_INFINITY, 4.0], @@ -363,6 +364,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn listview_float_nan_not_skipping() -> VortexResult<()> { let elements = PrimitiveArray::new( buffer![1.0f64, f64::NAN, 2.0, 3.0, 4.0], diff --git a/vortex-array/src/array/erased.rs b/vortex-array/src/array/erased.rs index fba1f44e56b..aac37835cc3 100644 --- a/vortex-array/src/array/erased.rs +++ b/vortex-array/src/array/erased.rs @@ -270,6 +270,7 @@ impl ArrayRef { encoding state across lookups: `array.probe(ProbeUsage::Once).execute_scalar(index, ctx)`." )] #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn scalar_at(&self, index: usize) -> VortexResult { self.execute_scalar(index, &mut legacy_session().create_execution_ctx()) } @@ -312,10 +313,9 @@ impl ArrayRef { } /// Returns whether the item at `index` is invalid. - #[deprecated( - note = "Use `ArrayRef::probe` instead: \ - `!array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)?`." - )] + #[deprecated(note = "Use `ArrayRef::probe` instead: \ + `!array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)?`.")] + #[expect(deprecated)] pub fn is_invalid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { Ok(!self.is_valid(index, ctx)?) } diff --git a/vortex-array/src/array/probe.rs b/vortex-array/src/array/probe.rs index 7421f619b13..5ef729d5d81 100644 --- a/vortex-array/src/array/probe.rs +++ b/vortex-array/src/array/probe.rs @@ -257,6 +257,7 @@ impl ValidityProbe<'_> { } /// Read a non-null boolean scalar indicating whether the requested row is valid. + #[expect(deprecated)] pub fn execute_scalar(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { vortex_ensure!(index < self.len, OutOfBounds: index, 0, self.len); match &mut self.inner { diff --git a/vortex-array/src/array/probe/tests.rs b/vortex-array/src/array/probe/tests.rs index 4c50de3672b..fa2cb3232d8 100644 --- a/vortex-array/src/array/probe/tests.rs +++ b/vortex-array/src/array/probe/tests.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +#![expect(deprecated)] + use std::cell::Cell; use std::rc::Rc; diff --git a/vortex-array/src/array/typed.rs b/vortex-array/src/array/typed.rs index 940a2cdfa3a..95fad0612cb 100644 --- a/vortex-array/src/array/typed.rs +++ b/vortex-array/src/array/typed.rs @@ -387,6 +387,7 @@ impl Array { encoding state across lookups: `array.probe(ProbeUsage::Once).execute_scalar(index, ctx)`." )] #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn scalar_at(&self, index: usize) -> VortexResult { self.inner .execute_scalar(index, &mut legacy_session().create_execution_ctx()) @@ -398,6 +399,7 @@ impl Array { `array.probe(ProbeUsage::Once).execute_scalar(index, ctx)`, or `ProbeUsage::Repeated` \ when reading more than one index from the same array." )] + #[expect(deprecated)] pub fn execute_scalar( &self, index: usize, @@ -432,15 +434,15 @@ impl Array { `array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)`, or `ProbeUsage::Repeated` \ when reading more than one index from the same array." )] + #[expect(deprecated)] pub fn is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { self.inner.is_valid(index, ctx) } /// Returns whether `index` is null using the provided execution context. - #[deprecated( - note = "Use `Array::::probe` instead: \ - `!array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)?`." - )] + #[deprecated(note = "Use `Array::::probe` instead: \ + `!array.probe(ProbeUsage::Once).execute_is_valid(index, ctx)?`.")] + #[expect(deprecated)] pub fn is_invalid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { self.inner.is_invalid(index, ctx) } diff --git a/vortex-array/src/array/vtable/operations.rs b/vortex-array/src/array/vtable/operations.rs index 385410feb7f..3f0f9cb5d10 100644 --- a/vortex-array/src/array/vtable/operations.rs +++ b/vortex-array/src/array/vtable/operations.rs @@ -33,6 +33,7 @@ pub trait OperationsVTable { /// The scalar must retain the source's logical dtype, including nullability. /// /// The default preserves the existing scalar path without adding caching. + #[expect(deprecated)] fn probe_scalar( array: ArrayView<'_, V>, index: usize, diff --git a/vortex-array/src/arrays/assertions.rs b/vortex-array/src/arrays/assertions.rs index aff8d4e32cb..9a3a6f0939d 100644 --- a/vortex-array/src/arrays/assertions.rs +++ b/vortex-array/src/arrays/assertions.rs @@ -27,6 +27,7 @@ fn execute_to_canonical(array: ArrayRef, ctx: &mut ExecutionCtx) -> ArrayRef { /// Finds indices where two arrays differ based on `scalar_at` comparison. #[expect(clippy::unwrap_used)] +#[expect(deprecated)] fn find_mismatched_indices( left: &ArrayRef, right: &ArrayRef, @@ -54,7 +55,9 @@ macro_rules! assert_nth_scalar { use $crate::IntoArray as _; let arr_ref: $crate::ArrayRef = $crate::IntoArray::into_array($arr.clone()); let expected = $expected.try_into().unwrap(); - assert_eq!(arr_ref.execute_scalar($n, $ctx).unwrap(), expected); + #[expect(deprecated)] + let actual = arr_ref.execute_scalar($n, $ctx).unwrap(); + assert_eq!(actual, expected); }}; } @@ -70,6 +73,7 @@ macro_rules! assert_nth_scalar { macro_rules! assert_nth_scalar_is_null { ($arr:expr, $n:expr, $ctx:expr) => {{ let arr_ref: $crate::ArrayRef = $crate::IntoArray::into_array($arr.clone()); + #[expect(deprecated)] let scalar = arr_ref.execute_scalar($n, $ctx).unwrap(); assert!( scalar.is_null(), diff --git a/vortex-array/src/arrays/bool/array.rs b/vortex-array/src/arrays/bool/array.rs index 5b1083e7f88..610c00e9abf 100644 --- a/vortex-array/src/arrays/bool/array.rs +++ b/vortex-array/src/arrays/bool/array.rs @@ -388,6 +388,7 @@ mod tests { use crate::validity::Validity; #[test] + #[expect(deprecated)] fn bool_array() { let mut ctx = array_session().create_execution_ctx(); @@ -397,6 +398,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_all_some_iter() { let mut ctx = array_session().create_execution_ctx(); @@ -411,6 +413,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_bool_from_iter() { let mut ctx = array_session().create_execution_ctx(); let arr = BoolArray::from_iter([Some(true), Some(true), None, Some(false), None]); diff --git a/vortex-array/src/arrays/chunked/tests.rs b/vortex-array/src/arrays/chunked/tests.rs index e0755108e9c..d903dbc2ae0 100644 --- a/vortex-array/src/arrays/chunked/tests.rs +++ b/vortex-array/src/arrays/chunked/tests.rs @@ -397,6 +397,7 @@ pub fn pack_nested_structs() -> VortexResult<()> { } #[test] +#[expect(deprecated)] pub fn pack_nested_lists() { let mut ctx = SESSION.create_execution_ctx(); let l1 = ListArray::try_new( diff --git a/vortex-array/src/arrays/chunked/vtable/canonical.rs b/vortex-array/src/arrays/chunked/vtable/canonical.rs index a521fd0e6cc..1adc5b2ddbb 100644 --- a/vortex-array/src/arrays/chunked/vtable/canonical.rs +++ b/vortex-array/src/arrays/chunked/vtable/canonical.rs @@ -371,6 +371,7 @@ mod tests { } } + #[expect(deprecated)] fn assert_variant_values(array: &VariantArray, expected: &[i32]) -> VortexResult<()> { assert_eq!(array.len(), expected.len()); let mut ctx = SESSION.create_execution_ctx(); @@ -575,6 +576,7 @@ mod tests { } #[test] + #[expect(deprecated)] pub fn pack_nested_lists() { let mut ctx = SESSION.create_execution_ctx(); let l1 = ListArray::try_new( @@ -614,6 +616,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn pack_fixed_size_lists() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let f1 = FixedSizeListArray::try_new( diff --git a/vortex-array/src/arrays/chunked/vtable/operations.rs b/vortex-array/src/arrays/chunked/vtable/operations.rs index 395e8aff70a..b8bfcdf3f1b 100644 --- a/vortex-array/src/arrays/chunked/vtable/operations.rs +++ b/vortex-array/src/arrays/chunked/vtable/operations.rs @@ -13,6 +13,7 @@ use crate::scalar::Scalar; impl OperationsVTable for Chunked { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Chunked>, index: usize, diff --git a/vortex-array/src/arrays/constant/compute/cast.rs b/vortex-array/src/arrays/constant/compute/cast.rs index 439bf8367b8..e3a093553cc 100644 --- a/vortex-array/src/arrays/constant/compute/cast.rs +++ b/vortex-array/src/arrays/constant/compute/cast.rs @@ -49,6 +49,7 @@ mod tests { #[test] #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] fn test_cast_constant_i64_to_decimal() { let target_dtype = DType::Decimal(DecimalDType::new(21, 2), Nullability::NonNullable); let casted = ConstantArray::new(Scalar::from(42i64), 5) diff --git a/vortex-array/src/arrays/constant/vtable/canonical.rs b/vortex-array/src/arrays/constant/vtable/canonical.rs index e6a656afa51..ec1e64a7532 100644 --- a/vortex-array/src/arrays/constant/vtable/canonical.rs +++ b/vortex-array/src/arrays/constant/vtable/canonical.rs @@ -394,6 +394,7 @@ mod tests { static SESSION: LazyLock = LazyLock::new(crate::array_session); #[test] + #[expect(deprecated)] fn test_canonicalize_null() { let mut ctx = SESSION.create_execution_ctx(); let const_null = ConstantArray::new(Scalar::null(DType::Null), 42); @@ -445,6 +446,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_canonicalize_scalar_values() { let mut ctx = SESSION.create_execution_ctx(); let f16_value = f16::from_f32(5.722046e-6); @@ -718,6 +720,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_canonicalize_fixed_size_list_nested() { let mut ctx = SESSION.create_execution_ctx(); // Test with nested data types (list of strings). @@ -782,6 +785,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_canonicalize_fixed_size_list_with_null_elements() { let mut ctx = SESSION.create_execution_ctx(); // Test FSL with nullable element type where some elements are null. @@ -836,6 +840,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_canonicalize_fixed_size_list_large() { let mut ctx = SESSION.create_execution_ctx(); // Test with a large constant array. diff --git a/vortex-array/src/arrays/constant/vtable/operations.rs b/vortex-array/src/arrays/constant/vtable/operations.rs index 9c49094f324..c95be262f0f 100644 --- a/vortex-array/src/arrays/constant/vtable/operations.rs +++ b/vortex-array/src/arrays/constant/vtable/operations.rs @@ -35,6 +35,7 @@ mod tests { use crate::scalar::Scalar; #[test] + #[expect(deprecated)] fn scalar_at_preserves_union_scalar() -> VortexResult<()> { let variants = UnionVariants::try_new( ["int", "string"].into(), diff --git a/vortex-array/src/arrays/decimal/compute/cast.rs b/vortex-array/src/arrays/decimal/compute/cast.rs index 8770a9bc8f4..251e6378c5d 100644 --- a/vortex-array/src/arrays/decimal/compute/cast.rs +++ b/vortex-array/src/arrays/decimal/compute/cast.rs @@ -876,6 +876,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn cast_decimal_f64_null() { let values = [Some(100i32), None, Some(-200)]; let array = DecimalArray::from_option_iter(values, DecimalDType::new(6, 2)); @@ -904,6 +905,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn cast_decimal_f64_all_null() { let dtype = DecimalDType::new(6, 2); let buf = buffer![i32::MAX, i32::MIN, 12345]; diff --git a/vortex-array/src/arrays/decimal/vtable/operations.rs b/vortex-array/src/arrays/decimal/vtable/operations.rs index aecf6638bd2..af628b3d8e1 100644 --- a/vortex-array/src/arrays/decimal/vtable/operations.rs +++ b/vortex-array/src/arrays/decimal/vtable/operations.rs @@ -74,6 +74,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_scalar_at() { let array = DecimalArray::new( buffer![100i128], diff --git a/vortex-array/src/arrays/dict/vtable/operations.rs b/vortex-array/src/arrays/dict/vtable/operations.rs index d497db0f2f8..d1380cc888b 100644 --- a/vortex-array/src/arrays/dict/vtable/operations.rs +++ b/vortex-array/src/arrays/dict/vtable/operations.rs @@ -14,6 +14,7 @@ use crate::scalar::Scalar; impl OperationsVTable for Dict { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Dict>, index: usize, diff --git a/vortex-array/src/arrays/extension/vtable/operations.rs b/vortex-array/src/arrays/extension/vtable/operations.rs index 519ef6088f7..11fd8d48d22 100644 --- a/vortex-array/src/arrays/extension/vtable/operations.rs +++ b/vortex-array/src/arrays/extension/vtable/operations.rs @@ -13,6 +13,7 @@ use crate::scalar::Scalar; impl OperationsVTable for Extension { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Extension>, index: usize, diff --git a/vortex-array/src/arrays/filter/execute/listview.rs b/vortex-array/src/arrays/filter/execute/listview.rs index ba15ee51998..7a5f25376e6 100644 --- a/vortex-array/src/arrays/filter/execute/listview.rs +++ b/vortex-array/src/arrays/filter/execute/listview.rs @@ -294,6 +294,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_filter_extreme_offsets() { // ListView-specific: Test with very large offsets. let elements = PrimitiveArray::from_iter(0i32..10000).into_array(); diff --git a/vortex-array/src/arrays/filter/vtable.rs b/vortex-array/src/arrays/filter/vtable.rs index 7f0e0bfd014..6d15d0afce7 100644 --- a/vortex-array/src/arrays/filter/vtable.rs +++ b/vortex-array/src/arrays/filter/vtable.rs @@ -202,6 +202,7 @@ impl VTable for Filter { impl OperationsVTable for Filter { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Filter>, index: usize, diff --git a/vortex-array/src/arrays/fixed_size_list/tests/basic.rs b/vortex-array/src/arrays/fixed_size_list/tests/basic.rs index b471ebb07a0..4fbd5503503 100644 --- a/vortex-array/src/arrays/fixed_size_list/tests/basic.rs +++ b/vortex-array/src/arrays/fixed_size_list/tests/basic.rs @@ -18,6 +18,7 @@ use crate::scalar::Scalar; use crate::validity::Validity; #[test] +#[expect(deprecated)] fn test_basic_fixed_size_list() { let len = 4; let list_size = 3; @@ -120,6 +121,7 @@ fn test_basic_fixed_size_list() { } #[test] +#[expect(deprecated)] fn test_scalar_at() { let len = 2; let list_size = 3; @@ -197,6 +199,7 @@ fn test_scalar_at() { } #[test] +#[expect(deprecated)] fn test_fixed_size_list_at() { let len = 3; let list_size = 2; diff --git a/vortex-array/src/arrays/fixed_size_list/tests/degenerate.rs b/vortex-array/src/arrays/fixed_size_list/tests/degenerate.rs index 73085b8c8a8..14486157514 100644 --- a/vortex-array/src/arrays/fixed_size_list/tests/degenerate.rs +++ b/vortex-array/src/arrays/fixed_size_list/tests/degenerate.rs @@ -44,6 +44,7 @@ fn test_fsl_size_0_length_0_non_nullable() { } #[test] +#[expect(deprecated)] fn test_fsl_size_0_length_1_non_nullable() { let len = 1; let list_size = 0; @@ -72,6 +73,7 @@ fn test_fsl_size_0_length_1_non_nullable() { } #[test] +#[expect(deprecated)] fn test_fsl_size_0_huge_length_non_nullable() { let len = 1_000_000_000_000; let list_size = 0; @@ -149,6 +151,7 @@ fn test_fsl_size_0_length_0_nullable() { } #[test] +#[expect(deprecated)] fn test_fsl_size_0_length_1_nullable_valid() { let len = 1; let list_size = 0; @@ -174,6 +177,7 @@ fn test_fsl_size_0_length_1_nullable_valid() { } #[test] +#[expect(deprecated)] fn test_fsl_size_0_length_1_nullable_null() { let len = 1; let list_size = 0; @@ -195,6 +199,7 @@ fn test_fsl_size_0_length_1_nullable_null() { } #[test] +#[expect(deprecated)] fn test_fsl_size_0_length_10_nullable_mixed() { let len = 10; let list_size = 0; @@ -235,6 +240,7 @@ fn test_fsl_size_0_length_10_nullable_mixed() { //////////////////////////////////////////////////////////////////////////////////////////////////// #[test] +#[expect(deprecated)] fn test_fsl_size_0_nullable_elements() { let len = 5; let list_size = 0; diff --git a/vortex-array/src/arrays/fixed_size_list/tests/nested.rs b/vortex-array/src/arrays/fixed_size_list/tests/nested.rs index b754229ff90..747def8614f 100644 --- a/vortex-array/src/arrays/fixed_size_list/tests/nested.rs +++ b/vortex-array/src/arrays/fixed_size_list/tests/nested.rs @@ -27,6 +27,7 @@ use crate::validity::Validity; //////////////////////////////////////////////////////////////////////////////////////////////////// #[test] +#[expect(deprecated)] fn test_fsl_of_fsl_basic() { let outer_len = 2; let outer_list_size = 3; @@ -205,6 +206,7 @@ fn test_fsl_of_fsl_basic() { } #[test] +#[expect(deprecated)] fn test_fsl_of_fsl_with_nulls() { let outer_len = 3; let outer_list_size = 2; @@ -271,6 +273,7 @@ fn test_fsl_of_fsl_with_nulls() { } #[test] +#[expect(deprecated)] fn test_deeply_nested_fsl() { let list_size = 2; diff --git a/vortex-array/src/arrays/fixed_size_list/tests/nullability.rs b/vortex-array/src/arrays/fixed_size_list/tests/nullability.rs index f5e5339e756..2c9964e810c 100644 --- a/vortex-array/src/arrays/fixed_size_list/tests/nullability.rs +++ b/vortex-array/src/arrays/fixed_size_list/tests/nullability.rs @@ -19,6 +19,7 @@ use crate::scalar::Scalar; use crate::validity::Validity; #[test] +#[expect(deprecated)] fn test_nullable_fsl_with_nulls() { let len = 4; let list_size = 2; @@ -103,6 +104,7 @@ fn test_nullable_fsl_with_nulls() { } #[test] +#[expect(deprecated)] fn test_nullable_elements_non_nullable_lists() { let len = 2; let list_size = 3; @@ -151,6 +153,7 @@ fn test_nullable_elements_non_nullable_lists() { } #[test] +#[expect(deprecated)] fn test_nullable_elements_and_nullable_lists() { let len = 3; let list_size = 2; @@ -229,6 +232,7 @@ fn test_nullable_elements_and_nullable_lists() { } #[test] +#[expect(deprecated)] fn test_alternating_nulls() { let len = 6; let list_size = 1; @@ -263,6 +267,7 @@ fn test_alternating_nulls() { } #[test] +#[expect(deprecated)] fn test_validity_types() { let len = 4; let list_size = 2; @@ -316,6 +321,7 @@ fn test_validity_types() { } #[test] +#[expect(deprecated)] fn test_mixed_nullability_patterns() { let len = 5; let list_size = 2; diff --git a/vortex-array/src/arrays/fixed_size_list/tests/take.rs b/vortex-array/src/arrays/fixed_size_list/tests/take.rs index 3904527abfd..c5e600c0773 100644 --- a/vortex-array/src/arrays/fixed_size_list/tests/take.rs +++ b/vortex-array/src/arrays/fixed_size_list/tests/take.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +#![expect(deprecated)] + use rstest::rstest; use vortex_buffer::Buffer; use vortex_buffer::buffer; diff --git a/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs b/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs index eca0e45ad95..39c049fea4a 100644 --- a/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs +++ b/vortex-array/src/arrays/fixed_size_list/vtable/operations.rs @@ -13,6 +13,7 @@ use crate::scalar::Scalar; impl OperationsVTable for FixedSizeList { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, FixedSizeList>, index: usize, diff --git a/vortex-array/src/arrays/interleave/mod.rs b/vortex-array/src/arrays/interleave/mod.rs index fb649cf6e19..e8bf09e293d 100644 --- a/vortex-array/src/arrays/interleave/mod.rs +++ b/vortex-array/src/arrays/interleave/mod.rs @@ -391,6 +391,7 @@ impl VTable for Interleave { impl OperationsVTable for Interleave { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Interleave>, index: usize, @@ -473,6 +474,7 @@ mod tests { /// /// This is deliberately *not* wired into the array execution path — it exists purely as a /// trustworthy comparison point in tests. + #[expect(deprecated)] fn interleave_reference( values: &[ArrayRef], array_indices: &ArrayRef, diff --git a/vortex-array/src/arrays/list/array.rs b/vortex-array/src/arrays/list/array.rs index 419617c073c..4465b4d0622 100644 --- a/vortex-array/src/arrays/list/array.rs +++ b/vortex-array/src/arrays/list/array.rs @@ -292,6 +292,7 @@ pub trait ListArrayExt: ListArraySlotsExt { } #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] fn offset_at(&self, index: usize) -> VortexResult { vortex_ensure!( index <= self.as_ref().len(), @@ -328,6 +329,7 @@ pub trait ListArrayExt: ListArraySlotsExt { self.elements().dtype() } + #[expect(deprecated)] fn reset_offsets(&self, recurse: bool, ctx: &mut ExecutionCtx) -> VortexResult> { let mut elements = self.sliced_elements()?; if recurse && elements.is_canonical() { diff --git a/vortex-array/src/arrays/list/compute/take.rs b/vortex-array/src/arrays/list/compute/take.rs index c010b0d2732..48a6630e17b 100644 --- a/vortex-array/src/arrays/list/compute/take.rs +++ b/vortex-array/src/arrays/list/compute/take.rs @@ -869,6 +869,7 @@ mod test { use crate::validity::Validity; #[test] + #[expect(deprecated)] fn nullable_take() { let mut ctx = array_session().create_execution_ctx(); let list = ListArray::try_new( @@ -1020,6 +1021,7 @@ mod test { } #[test] + #[expect(deprecated)] fn non_nullable_take() { let mut ctx = array_session().create_execution_ctx(); let list = ListArray::try_new( @@ -1094,6 +1096,7 @@ mod test { } #[test] + #[expect(deprecated)] fn piecewise_sequence_take() { let mut ctx = array_session().create_execution_ctx(); let list = ListArray::try_new( @@ -1142,6 +1145,7 @@ mod test { } #[test] + #[expect(deprecated)] fn piecewise_sequence_take_nullable_list_constant_lengths() -> VortexResult<()> { let mut ctx = array_session().create_execution_ctx(); let list = ListArray::try_new( @@ -1194,6 +1198,7 @@ mod test { } #[test] + #[expect(deprecated)] fn piecewise_sequence_take_nullable_list_array_lengths() -> VortexResult<()> { let mut ctx = array_session().create_execution_ctx(); let list = ListArray::try_new( @@ -1308,6 +1313,7 @@ mod test { } #[test] + #[expect(deprecated)] fn test_u64_offset_accumulation_non_nullable() { let mut ctx = array_session().create_execution_ctx(); let elements = buffer![0i32; 200].into_array(); @@ -1337,6 +1343,7 @@ mod test { } #[test] + #[expect(deprecated)] fn test_u64_offset_accumulation_nullable() { let mut ctx = array_session().create_execution_ctx(); let elements = buffer![0i32; 150].into_array(); diff --git a/vortex-array/src/arrays/list/tests.rs b/vortex-array/src/arrays/list/tests.rs index 93daf3dc4a4..ad2e2365c58 100644 --- a/vortex-array/src/arrays/list/tests.rs +++ b/vortex-array/src/arrays/list/tests.rs @@ -43,6 +43,7 @@ fn test_empty_list_array() { } #[test] +#[expect(deprecated)] fn test_simple_list_array() { let mut ctx = SESSION.create_execution_ctx(); let elements = buffer![1i32, 2, 3, 4, 5].into_array(); @@ -74,6 +75,7 @@ fn test_simple_list_array() { } #[test] +#[expect(deprecated)] fn test_simple_list_array_from_iter() { let mut ctx = SESSION.create_execution_ctx(); let elements = buffer![1i32, 2, 3].into_array(); @@ -208,6 +210,7 @@ fn test_list_filter_empty_lists() { } #[test] +#[expect(deprecated)] fn test_list_filter_with_nulls() { let mut ctx = SESSION.create_execution_ctx(); // Test filtering lists with null validity. @@ -586,6 +589,7 @@ fn create_list_of_lists_nullable(data: OptVec>>) -> ListArray #[test] #[expect(clippy::cognitive_complexity)] +#[expect(deprecated)] fn test_list_of_lists() { let mut ctx = SESSION.create_execution_ctx(); let data = vec![ @@ -670,6 +674,7 @@ fn test_list_of_lists() { } #[test] +#[expect(deprecated)] fn test_list_of_lists_nullable_outer() { let mut ctx = SESSION.create_execution_ctx(); // Create list of lists with nullable outer, non-nullable inner. @@ -715,6 +720,7 @@ fn test_list_of_lists_nullable_outer() { } #[test] +#[expect(deprecated)] fn test_list_of_lists_nullable_inner() { // Create list of lists with non-nullable outer, nullable inner. // Structure: [[[1, 2], null, [3]], [[4, 5, 6]], [], [[null, 7]]] @@ -759,6 +765,7 @@ fn test_list_of_lists_nullable_inner() { } #[test] +#[expect(deprecated)] fn test_list_of_lists_both_nullable() { let mut ctx = SESSION.create_execution_ctx(); // Create list of lists with both nullable. @@ -904,6 +911,7 @@ fn test_offsets_constant() { } #[test] +#[expect(deprecated)] fn test_recursive_compact_list_of_lists() { let mut ctx = SESSION.create_execution_ctx(); // Create a nested list structure: [[[1,2,3], [4,5]], [[6,7,8,9]], [[10], [11,12]]] diff --git a/vortex-array/src/arrays/list/vtable/operations.rs b/vortex-array/src/arrays/list/vtable/operations.rs index 668cc18a32f..c8f7137304c 100644 --- a/vortex-array/src/arrays/list/vtable/operations.rs +++ b/vortex-array/src/arrays/list/vtable/operations.rs @@ -15,6 +15,7 @@ use crate::scalar::Scalar; impl OperationsVTable for List { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, List>, index: usize, diff --git a/vortex-array/src/arrays/listview/array.rs b/vortex-array/src/arrays/listview/array.rs index 43f51971b8f..dd68e11beca 100644 --- a/vortex-array/src/arrays/listview/array.rs +++ b/vortex-array/src/arrays/listview/array.rs @@ -369,6 +369,7 @@ pub trait ListViewArrayExt: ListViewArraySlotsExt { } #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] fn offset_at(&self, index: usize) -> usize { assert!( index < self.as_ref().len(), @@ -389,6 +390,7 @@ pub trait ListViewArrayExt: ListViewArraySlotsExt { } #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] fn size_at(&self, index: usize) -> usize { assert!( index < self.as_ref().len(), diff --git a/vortex-array/src/arrays/listview/rebuild.rs b/vortex-array/src/arrays/listview/rebuild.rs index dfcfc4283c7..727f458235c 100644 --- a/vortex-array/src/arrays/listview/rebuild.rs +++ b/vortex-array/src/arrays/listview/rebuild.rs @@ -583,6 +583,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_rebuild_trim_elements_basic() -> VortexResult<()> { // Test trimming both leading and trailing unused elements while preserving gaps in the // middle. @@ -662,6 +663,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_rebuild_with_trailing_nulls_regression() -> VortexResult<()> { // Regression test for issue #5412 // Tests that zero-copy-to-list arrays with trailing NULLs correctly calculate diff --git a/vortex-array/src/arrays/listview/tests/basic.rs b/vortex-array/src/arrays/listview/tests/basic.rs index f3082757ca0..429c1df2414 100644 --- a/vortex-array/src/arrays/listview/tests/basic.rs +++ b/vortex-array/src/arrays/listview/tests/basic.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +#![expect(deprecated)] + use std::sync::Arc; use rstest::rstest; diff --git a/vortex-array/src/arrays/listview/tests/filter.rs b/vortex-array/src/arrays/listview/tests/filter.rs index d8a6cb4f4db..2e91f857b16 100644 --- a/vortex-array/src/arrays/listview/tests/filter.rs +++ b/vortex-array/src/arrays/listview/tests/filter.rs @@ -170,6 +170,7 @@ fn test_filter_constant_arrays() { } #[test] +#[expect(deprecated)] fn test_filter_extreme_offsets() { // ListView-specific: Test with very large offsets. let elements = PrimitiveArray::from_iter(0i32..10000).into_array(); diff --git a/vortex-array/src/arrays/listview/tests/nested.rs b/vortex-array/src/arrays/listview/tests/nested.rs index 4b2676f4491..814a78a8def 100644 --- a/vortex-array/src/arrays/listview/tests/nested.rs +++ b/vortex-array/src/arrays/listview/tests/nested.rs @@ -23,6 +23,7 @@ use crate::validity::Validity; //////////////////////////////////////////////////////////////////////////////////////////////////// #[test] +#[expect(deprecated)] fn test_listview_of_listview_with_overlapping() { // Create elements that will be shared between inner lists. // Elements: [1, 2, 3, 4, 5, 6, 7, 8] @@ -239,6 +240,7 @@ fn test_mixed_offset_size_types() { //////////////////////////////////////////////////////////////////////////////////////////////////// #[test] +#[expect(deprecated)] fn test_listview_zero_and_overlapping() { // Mix of empty lists, overlapping lists, and normal lists. let elements = buffer![1i32, 2, 3, 4, 5].into_array(); @@ -324,6 +326,7 @@ fn test_listview_zero_and_overlapping() { //////////////////////////////////////////////////////////////////////////////////////////////////// #[test] +#[expect(deprecated)] fn test_listview_of_struct_with_nulls() { // Create structs with fields that could be null. let struct_fields = StructFields::new( diff --git a/vortex-array/src/arrays/listview/tests/nullability.rs b/vortex-array/src/arrays/listview/tests/nullability.rs index b32e2052435..af42edb576a 100644 --- a/vortex-array/src/arrays/listview/tests/nullability.rs +++ b/vortex-array/src/arrays/listview/tests/nullability.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +#![expect(deprecated)] + use std::sync::Arc; use rstest::rstest; diff --git a/vortex-array/src/arrays/listview/tests/operations.rs b/vortex-array/src/arrays/listview/tests/operations.rs index 38ac286b2d1..e3e375442dd 100644 --- a/vortex-array/src/arrays/listview/tests/operations.rs +++ b/vortex-array/src/arrays/listview/tests/operations.rs @@ -36,6 +36,7 @@ use crate::validity::Validity; //////////////////////////////////////////////////////////////////////////////////////////////////// #[test] +#[expect(deprecated)] fn test_slice_comprehensive() { // Comprehensive test for basic slicing, full array, and single element cases. // Logical lists: [[1,2,3], [4,5], [6,7,8], [9,10]] @@ -139,6 +140,7 @@ fn test_slice_out_of_order() { } #[test] +#[expect(deprecated)] fn test_slice_with_nulls() { // Test slicing with nullable ListView. // Logical lists: [[1,2], null, [5,6], null] @@ -274,6 +276,7 @@ fn test_cast_numeric_types(#[case] from_ptype: PType, #[case] to_ptype: PType) { } #[test] +#[expect(deprecated)] fn test_cast_with_nulls() { // Logical lists: [[10,20], null] let elements = buffer![10i32, 20, 30, 40].into_array(); @@ -632,6 +635,7 @@ fn test_mask_listview_conformance(#[case] listview: ListViewArray) { } #[test] +#[expect(deprecated)] fn test_mask_preserves_structure() { // ListView-specific: Verify mask preserves offsets and sizes. // Logical lists: [[1,2], [3,4], [5,6], [7,8]] @@ -688,6 +692,7 @@ fn test_mask_preserves_structure() { } #[test] +#[expect(deprecated)] fn test_mask_with_existing_nulls() { // ListView-specific: Test interaction between existing nulls and mask. // Logical lists: [[10,20], null, [50,60]] @@ -728,6 +733,7 @@ fn test_mask_with_existing_nulls() { } #[test] +#[expect(deprecated)] fn test_mask_with_gaps() { // ListView-specific: Mask with gaps in elements. // Logical lists: [[1,2], [5,6], [9,10]] (999 values are gaps) @@ -766,6 +772,7 @@ fn test_mask_with_gaps() { } #[test] +#[expect(deprecated)] fn test_mask_constant_arrays() { // ListView-specific: Test mask with ConstantArray offsets/sizes. // Logical lists: [[200,300], [200,300], [200,300]] diff --git a/vortex-array/src/arrays/listview/tests/take.rs b/vortex-array/src/arrays/listview/tests/take.rs index 6f74081a8c5..51227cf4ad3 100644 --- a/vortex-array/src/arrays/listview/tests/take.rs +++ b/vortex-array/src/arrays/listview/tests/take.rs @@ -159,6 +159,7 @@ fn test_take_constant_arrays() { } #[test] +#[expect(deprecated)] fn test_take_extreme_offsets() { // ListView-specific: Test with very large offsets to demonstrate // that we keep unreferenced elements. diff --git a/vortex-array/src/arrays/listview/vtable/operations.rs b/vortex-array/src/arrays/listview/vtable/operations.rs index 8608985463d..c2eefc3e32a 100644 --- a/vortex-array/src/arrays/listview/vtable/operations.rs +++ b/vortex-array/src/arrays/listview/vtable/operations.rs @@ -15,6 +15,7 @@ use crate::scalar::Scalar; impl OperationsVTable for ListView { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, ListView>, index: usize, diff --git a/vortex-array/src/arrays/map/tests.rs b/vortex-array/src/arrays/map/tests.rs index e1df51ce6d3..43f7d407d82 100644 --- a/vortex-array/src/arrays/map/tests.rs +++ b/vortex-array/src/arrays/map/tests.rs @@ -142,6 +142,7 @@ fn constructs_map_with_listview_entries() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn accepts_duplicate_and_unsorted_keys() -> VortexResult<()> { let array = sample_array()?; let mut ctx = array_session().create_execution_ctx(); @@ -205,6 +206,7 @@ fn rejects_malformed_entry_storage() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn scalar_access_preserves_null_and_empty_maps() -> VortexResult<()> { let array = sample_array()?; let mut ctx = array_session().create_execution_ctx(); @@ -220,6 +222,7 @@ fn scalar_access_preserves_null_and_empty_maps() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn scalar_access_preserves_variable_entry_counts_and_utf8_pairs() -> VortexResult<()> { let map_dtype = MapDType::try_new( DType::Utf8(Nullability::NonNullable), @@ -652,6 +655,7 @@ fn filter_preserves_duplicate_map_keys() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn builder_appends_existing_map_arrays() -> VortexResult<()> { let source = sample_array()?; let mut builder = MapBuilder::::with_capacity_in( @@ -702,6 +706,7 @@ fn canonicalizes_empty_constant_and_chunked_maps() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn serde_roundtrip_uses_registered_map_vtable() -> VortexResult<()> { let session = array_session(); assert!(session.arrays().registry().contains_key(&Map.id())); diff --git a/vortex-array/src/arrays/map/vtable/operations.rs b/vortex-array/src/arrays/map/vtable/operations.rs index 66b8e47a75a..c58d1de0ab9 100644 --- a/vortex-array/src/arrays/map/vtable/operations.rs +++ b/vortex-array/src/arrays/map/vtable/operations.rs @@ -15,6 +15,7 @@ use crate::scalar::Scalar; impl OperationsVTable for Map { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Map>, index: usize, diff --git a/vortex-array/src/arrays/masked/tests.rs b/vortex-array/src/arrays/masked/tests.rs index b326380bbf4..b380b5af362 100644 --- a/vortex-array/src/arrays/masked/tests.rs +++ b/vortex-array/src/arrays/masked/tests.rs @@ -77,6 +77,7 @@ fn test_canonical_dtype_matches_array_dtype() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn test_masked_child_with_validity() { // When validity has nulls, masked_child should apply inverted mask. let child = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5]).into_array(); diff --git a/vortex-array/src/arrays/masked/vtable/canonical.rs b/vortex-array/src/arrays/masked/vtable/canonical.rs index 32a5e334afa..00c6524cd5f 100644 --- a/vortex-array/src/arrays/masked/vtable/canonical.rs +++ b/vortex-array/src/arrays/masked/vtable/canonical.rs @@ -48,6 +48,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_canonical_with_nulls() -> VortexResult<()> { let array = MaskedArray::try_new( PrimitiveArray::from_iter([1i32, 2, 3, 4, 5]).into_array(), diff --git a/vortex-array/src/arrays/masked/vtable/operations.rs b/vortex-array/src/arrays/masked/vtable/operations.rs index a418e23f217..44537d272d3 100644 --- a/vortex-array/src/arrays/masked/vtable/operations.rs +++ b/vortex-array/src/arrays/masked/vtable/operations.rs @@ -13,6 +13,7 @@ use crate::scalar::Scalar; impl OperationsVTable for Masked { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Masked>, index: usize, diff --git a/vortex-array/src/arrays/null/compute/cast.rs b/vortex-array/src/arrays/null/compute/cast.rs index c1058156127..da0c7c714d3 100644 --- a/vortex-array/src/arrays/null/compute/cast.rs +++ b/vortex-array/src/arrays/null/compute/cast.rs @@ -50,6 +50,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_cast_null_to_nullable_succeeds() { let null_array = NullArray::new(5); let result = null_array diff --git a/vortex-array/src/arrays/null/compute/mod.rs b/vortex-array/src/arrays/null/compute/mod.rs index 73a23fe7a8d..cd88d074cc8 100644 --- a/vortex-array/src/arrays/null/compute/mod.rs +++ b/vortex-array/src/arrays/null/compute/mod.rs @@ -70,6 +70,7 @@ mod test { } #[test] + #[expect(deprecated)] fn test_scalar_at_nulls() { let nulls = NullArray::new(10); diff --git a/vortex-array/src/arrays/patched/array.rs b/vortex-array/src/arrays/patched/array.rs index 4522c8223b8..9f1ee91ed73 100644 --- a/vortex-array/src/arrays/patched/array.rs +++ b/vortex-array/src/arrays/patched/array.rs @@ -115,6 +115,7 @@ pub trait PatchedArrayExt: PatchedArraySlotsExt { #[inline] #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] fn lane_range(&self, chunk: usize, lane: usize) -> VortexResult> { assert!(chunk * 1024 <= self.as_ref().len() + self.offset()); assert!(lane < self.n_lanes()); diff --git a/vortex-array/src/arrays/patched/vtable/operations.rs b/vortex-array/src/arrays/patched/vtable/operations.rs index bd7440965ab..9c72817c5ff 100644 --- a/vortex-array/src/arrays/patched/vtable/operations.rs +++ b/vortex-array/src/arrays/patched/vtable/operations.rs @@ -16,6 +16,7 @@ use crate::scalar::Scalar; impl OperationsVTable for Patched { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Patched>, index: usize, @@ -72,6 +73,7 @@ mod tests { use crate::scalar::Scalar; #[test] + #[expect(deprecated)] fn test_simple() { let values = buffer![0u16; 1024].into_array(); let patches = Patches::new( @@ -117,6 +119,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_multi_chunk() { let values = buffer![0u16; 4096].into_array(); let patches = Patches::new( @@ -149,6 +152,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_multi_chunk_sliced() { let values = buffer![0u16; 4096].into_array(); let patches = Patches::new( diff --git a/vortex-array/src/arrays/piecewise_sequence/tests.rs b/vortex-array/src/arrays/piecewise_sequence/tests.rs index a087404fee2..d0999391a5b 100644 --- a/vortex-array/src/arrays/piecewise_sequence/tests.rs +++ b/vortex-array/src/arrays/piecewise_sequence/tests.rs @@ -103,6 +103,7 @@ fn supports_constant_lengths() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn scalar_at_maps_into_piece() -> VortexResult<()> { let starts = buffer![3u64, 15, 21].into_array(); let lengths = buffer![3u64, 3, 3].into_array(); diff --git a/vortex-array/src/arrays/scalar_fn/vtable/operations.rs b/vortex-array/src/arrays/scalar_fn/vtable/operations.rs index 21af5728572..9f24d4ba153 100644 --- a/vortex-array/src/arrays/scalar_fn/vtable/operations.rs +++ b/vortex-array/src/arrays/scalar_fn/vtable/operations.rs @@ -17,6 +17,7 @@ use crate::scalar_fn::VecExecutionArgs; impl OperationsVTable for ScalarFn { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, ScalarFn>, index: usize, diff --git a/vortex-array/src/arrays/shared/vtable.rs b/vortex-array/src/arrays/shared/vtable.rs index b1a9a4d7b15..ca4e3c62d65 100644 --- a/vortex-array/src/arrays/shared/vtable.rs +++ b/vortex-array/src/arrays/shared/vtable.rs @@ -127,6 +127,7 @@ impl VTable for Shared { impl OperationsVTable for Shared { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Shared>, index: usize, diff --git a/vortex-array/src/arrays/slice/vtable.rs b/vortex-array/src/arrays/slice/vtable.rs index c09d81ee4f1..fa9a889703f 100644 --- a/vortex-array/src/arrays/slice/vtable.rs +++ b/vortex-array/src/arrays/slice/vtable.rs @@ -171,6 +171,7 @@ impl VTable for Slice { impl OperationsVTable for Slice { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Slice>, index: usize, diff --git a/vortex-array/src/arrays/struct_/vtable/operations.rs b/vortex-array/src/arrays/struct_/vtable/operations.rs index 54d086bf7c9..860353d7670 100644 --- a/vortex-array/src/arrays/struct_/vtable/operations.rs +++ b/vortex-array/src/arrays/struct_/vtable/operations.rs @@ -14,6 +14,7 @@ use crate::scalar::ScalarValue; impl OperationsVTable for Struct { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Struct>, index: usize, diff --git a/vortex-array/src/arrays/union/tests/mod.rs b/vortex-array/src/arrays/union/tests/mod.rs index fc0abec37b8..354c203d769 100644 --- a/vortex-array/src/arrays/union/tests/mod.rs +++ b/vortex-array/src/arrays/union/tests/mod.rs @@ -78,6 +78,7 @@ fn nullable_union_array() -> VortexResult { } #[test] +#[expect(deprecated)] fn scalar_at_uses_type_id_indirection() -> VortexResult<()> { let array = union_array()?; let mut ctx = array_session().create_execution_ctx(); @@ -140,6 +141,7 @@ fn validates_sparse_components() -> VortexResult<()> { #[test] #[should_panic(expected = "Unknown UnionArray type ID 7")] +#[expect(deprecated)] fn invalid_type_id_panics_when_accessed() { let array = UnionArray::try_new( PrimitiveArray::from_iter([5u8, 7, 5]).into_array(), @@ -158,6 +160,7 @@ fn invalid_type_id_panics_when_accessed() { } #[test] +#[expect(deprecated)] fn outer_nulls_are_independent_from_inner_nulls() -> VortexResult<()> { let variants = nullable_variants()?; let array = nullable_union_array()?; @@ -193,6 +196,7 @@ fn outer_nulls_are_independent_from_inner_nulls() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn masking_adds_outer_nulls_only() -> VortexResult<()> { let masked = union_array()? .into_array() @@ -221,6 +225,7 @@ fn masking_adds_outer_nulls_only() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn slice_and_filter_preserve_sparse_alignment() -> VortexResult<()> { let array = union_array()?.into_array(); let mut ctx = array_session().create_execution_ctx(); @@ -243,6 +248,7 @@ fn slice_and_filter_preserve_sparse_alignment() -> VortexResult<()> { /// A constant union canonicalizes into a sparse union whose selected child repeats the scalar's /// value and whose unselected children hold placeholders. #[test] +#[expect(deprecated)] fn constant_union_canonicalizes_to_sparse_union() -> VortexResult<()> { let mut ctx = array_session().create_execution_ctx(); let i32_variant = DType::Primitive(PType::I32, Nullability::NonNullable); @@ -303,6 +309,7 @@ fn constant_union_reports_uncompressed_size() -> VortexResult<()> { } #[test] +#[expect(deprecated)] fn serde_roundtrip() -> VortexResult<()> { let session = array_session(); let mut execution_ctx = session.create_execution_ctx(); diff --git a/vortex-array/src/arrays/union/tests/take.rs b/vortex-array/src/arrays/union/tests/take.rs index 0088ace43b2..25108cd76b5 100644 --- a/vortex-array/src/arrays/union/tests/take.rs +++ b/vortex-array/src/arrays/union/tests/take.rs @@ -38,6 +38,7 @@ fn take(array: &UnionArray, indices: ArrayRef) -> VortexResult { /// Assert that `array` holds exactly `expected`, row for row. #[track_caller] +#[expect(deprecated)] fn assert_rows(array: &UnionArray, expected: Vec) -> VortexResult<()> { let mut ctx = array_session().create_execution_ctx(); diff --git a/vortex-array/src/arrays/union/vtable/operations.rs b/vortex-array/src/arrays/union/vtable/operations.rs index 78b9759ab1c..8325402d63a 100644 --- a/vortex-array/src/arrays/union/vtable/operations.rs +++ b/vortex-array/src/arrays/union/vtable/operations.rs @@ -16,6 +16,7 @@ use crate::scalar::Scalar; impl OperationsVTable for Union { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Union>, index: usize, diff --git a/vortex-array/src/arrays/varbin/array.rs b/vortex-array/src/arrays/varbin/array.rs index a763fb4f1de..228af74f7c4 100644 --- a/vortex-array/src/arrays/varbin/array.rs +++ b/vortex-array/src/arrays/varbin/array.rs @@ -335,6 +335,7 @@ pub trait VarBinArrayExt: VarBinArraySlotsExt { } #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] fn offset_at(&self, index: usize) -> usize { assert!( index <= self.as_ref().len(), diff --git a/vortex-array/src/arrays/varbin/builder.rs b/vortex-array/src/arrays/varbin/builder.rs index d915efdf2fd..110c402b00c 100644 --- a/vortex-array/src/arrays/varbin/builder.rs +++ b/vortex-array/src/arrays/varbin/builder.rs @@ -753,6 +753,7 @@ mod tests { use crate::scalar::Scalar; #[test] + #[expect(deprecated)] fn test_builder() { let mut builder = VarBinBuilder::::with_capacity_in( DType::Utf8(Nullable), diff --git a/vortex-array/src/arrays/varbin/vtable/canonical.rs b/vortex-array/src/arrays/varbin/vtable/canonical.rs index 825d920f1c7..9480c403aa0 100644 --- a/vortex-array/src/arrays/varbin/vtable/canonical.rs +++ b/vortex-array/src/arrays/varbin/vtable/canonical.rs @@ -43,6 +43,7 @@ pub(crate) fn varbin_to_canonical( #[cfg(test)] mod tests { + #![expect(deprecated)] use rstest::rstest; use vortex_error::VortexResult; diff --git a/vortex-array/src/arrays/variant/mod.rs b/vortex-array/src/arrays/variant/mod.rs index b5610320719..15dd9fec072 100644 --- a/vortex-array/src/arrays/variant/mod.rs +++ b/vortex-array/src/arrays/variant/mod.rs @@ -168,6 +168,7 @@ mod tests { Ok(()) } + #[expect(deprecated)] fn assert_variant_core_rows( array: &VariantArray, expected_core: &[Option], @@ -247,6 +248,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn scalar_at_merges_shredded_with_core_storage() -> VortexResult<()> { let dtype = DType::Variant(Nullability::Nullable); let core_chunks = [Some(1i32), None, Some(3)] diff --git a/vortex-array/src/arrays/variant/vtable/kernel.rs b/vortex-array/src/arrays/variant/vtable/kernel.rs index 22d52ecd6a7..a1cd61f51aa 100644 --- a/vortex-array/src/arrays/variant/vtable/kernel.rs +++ b/vortex-array/src/arrays/variant/vtable/kernel.rs @@ -169,6 +169,7 @@ fn typed_shredded_path( Ok(Some(current)) } +#[expect(deprecated)] fn merge_typed_as_variant( typed: ArrayRef, fallback: Option, diff --git a/vortex-array/src/arrays/variant/vtable/operations.rs b/vortex-array/src/arrays/variant/vtable/operations.rs index ed61223d637..d22f8acaf8d 100644 --- a/vortex-array/src/arrays/variant/vtable/operations.rs +++ b/vortex-array/src/arrays/variant/vtable/operations.rs @@ -14,6 +14,7 @@ use crate::scalar::Scalar; impl OperationsVTable for Variant { type ProbeState = (); + #[expect(deprecated)] fn scalar_at( array: ArrayView<'_, Variant>, index: usize, diff --git a/vortex-array/src/builders/decimal.rs b/vortex-array/src/builders/decimal.rs index d65dc41ad4a..e3990dc3613 100644 --- a/vortex-array/src/builders/decimal.rs +++ b/vortex-array/src/builders/decimal.rs @@ -341,6 +341,7 @@ mod tests { use crate::dtype::DecimalDType; #[test] + #[expect(deprecated)] fn test_mixed_extend() { let values = 42i8; @@ -375,6 +376,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_append_scalar() { let mut ctx = array_session().create_execution_ctx(); use crate::scalar::Scalar; diff --git a/vortex-array/src/builders/fixed_size_list.rs b/vortex-array/src/builders/fixed_size_list.rs index e9a66b62d83..dd9808706f4 100644 --- a/vortex-array/src/builders/fixed_size_list.rs +++ b/vortex-array/src/builders/fixed_size_list.rs @@ -1114,6 +1114,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_append_scalar() { let mut ctx = array_session().create_execution_ctx(); let dtype: Arc = Arc::new(I32.into()); diff --git a/vortex-array/src/builders/list.rs b/vortex-array/src/builders/list.rs index 5e97f122cba..6ee6c638d1d 100644 --- a/vortex-array/src/builders/list.rs +++ b/vortex-array/src/builders/list.rs @@ -842,6 +842,7 @@ mod tests { } #[test] + #[expect(deprecated)] pub fn test_array_with_gap() { let one_trailing_unused_element = ListArray::try_new( buffer![1, 2, 3, 4].into_array(), @@ -892,6 +893,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_append_scalar() { let dtype: Arc = Arc::new(I32.into()); let mut builder = ListBuilder::::with_capacity_in( diff --git a/vortex-array/src/builders/primitive.rs b/vortex-array/src/builders/primitive.rs index 647854176c5..8940611387a 100644 --- a/vortex-array/src/builders/primitive.rs +++ b/vortex-array/src/builders/primitive.rs @@ -437,6 +437,7 @@ mod tests { /// The old API had `append_mask` on the builder, which was confusing when used with ranges. /// This test ensures the new API works correctly. #[test] + #[expect(deprecated)] fn test_append_mask_on_uninit_range() { let mut builder = PrimitiveBuilder::::with_capacity_in( Nullability::Nullable, @@ -545,6 +546,7 @@ mod tests { /// use `append_mask` to set up the buffer, then demonstrate that `set_bit` can /// modify individual bits with relative indexing. #[test] + #[expect(deprecated)] fn test_set_bit_relative_indexing() { let mut builder = PrimitiveBuilder::::with_capacity_in( Nullability::Nullable, diff --git a/vortex-array/src/builders/tests.rs b/vortex-array/src/builders/tests.rs index 5b7977a0ef0..fcfc7f594ba 100644 --- a/vortex-array/src/builders/tests.rs +++ b/vortex-array/src/builders/tests.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +#![expect(deprecated)] + use std::alloc::Layout; use std::ptr::NonNull; use std::sync::Arc; diff --git a/vortex-array/src/builders/varbinview.rs b/vortex-array/src/builders/varbinview.rs index e27336fe998..b561e1bf264 100644 --- a/vortex-array/src/builders/varbinview.rs +++ b/vortex-array/src/builders/varbinview.rs @@ -2044,6 +2044,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_large_value_allocation() { use super::BufferGrowthStrategy; use super::VarBinViewBuilder; diff --git a/vortex-array/src/compute/conformance/binary_numeric.rs b/vortex-array/src/compute/conformance/binary_numeric.rs index 1750ffc8e5d..41ef01de6cf 100644 --- a/vortex-array/src/compute/conformance/binary_numeric.rs +++ b/vortex-array/src/compute/conformance/binary_numeric.rs @@ -55,6 +55,7 @@ use crate::scalar::PrimitiveScalar; use crate::scalar::Scalar; use crate::scalar_fn::fns::binary::numeric_op_result_decimal_dtype; +#[expect(deprecated)] fn to_vec_of_scalar(array: &ArrayRef, ctx: &mut ExecutionCtx) -> Vec { // Not fast, but obviously correct (0..array.len()) diff --git a/vortex-array/src/compute/conformance/cast.rs b/vortex-array/src/compute/conformance/cast.rs index 6f9bd4aefd0..0c7dfa2746a 100644 --- a/vortex-array/src/compute/conformance/cast.rs +++ b/vortex-array/src/compute/conformance/cast.rs @@ -67,6 +67,7 @@ pub fn test_cast_conformance(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_cast_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) { // Casting to the same type should be a no-op let result = cast_and_execute(&array.clone(), array.dtype().clone(), ctx) @@ -87,6 +88,7 @@ fn test_cast_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_cast_from_null(array: &ArrayRef, ctx: &mut ExecutionCtx) { // Null can be cast to itself let result = cast_and_execute(&array.clone(), DType::Null, ctx) @@ -131,6 +133,7 @@ fn test_cast_from_null(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_cast_to_non_nullable(array: &ArrayRef, ctx: &mut ExecutionCtx) { // DType::Null has no non-nullable form if &DType::Null == array.dtype() { @@ -190,6 +193,7 @@ fn test_cast_to_non_nullable(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_cast_to_nullable(array: &ArrayRef, ctx: &mut ExecutionCtx) { let nullable = cast_and_execute(&array.clone(), array.dtype().as_nullable(), ctx) .vortex_expect("arrays without nulls can cast to nullable"); @@ -260,6 +264,7 @@ fn fits(value: &Scalar, ptype: PType) -> bool { value.cast(&dtype).is_ok() } +#[expect(deprecated)] fn test_cast_to_primitive( array: &ArrayRef, target_ptype: PType, diff --git a/vortex-array/src/compute/conformance/consistency.rs b/vortex-array/src/compute/conformance/consistency.rs index cc1d826bdcf..7f02fb6765f 100644 --- a/vortex-array/src/compute/conformance/consistency.rs +++ b/vortex-array/src/compute/conformance/consistency.rs @@ -50,6 +50,7 @@ use crate::scalar_fn::fns::operators::Operator; /// - Creates indices array containing positions where mask is true /// - Applies take with these indices /// - Verifies both results are identical +#[expect(deprecated)] fn test_filter_take_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -118,6 +119,7 @@ fn test_filter_take_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { /// # Why This Matters /// This test ensures that mask operations compose correctly, which is critical for /// complex query operations that may apply multiple filters. +#[expect(deprecated)] fn test_double_mask_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -191,6 +193,7 @@ fn test_double_mask_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { /// # Why This Matters /// This is an identity operation that should be optimized in implementations /// to avoid unnecessary copying. +#[expect(deprecated)] fn test_filter_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -240,6 +243,7 @@ fn test_filter_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) { /// # Why This Matters /// Masking always produces a nullable array, even when no values are actually masked. /// This test ensures the type system handles this correctly. +#[expect(deprecated)] fn test_mask_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -298,6 +302,7 @@ fn test_mask_identity(array: &ArrayRef, ctx: &mut ExecutionCtx) { /// # Why This Matters /// When a filter mask represents a contiguous range, it should be equivalent to /// a slice operation. Some implementations may optimize this case. +#[expect(deprecated)] fn test_slice_filter_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 4 { @@ -355,6 +360,7 @@ fn test_slice_filter_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { /// /// # Why This Matters /// Sequential takes are a common pattern that can be optimized to slice operations. +#[expect(deprecated)] fn test_take_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 3 { @@ -398,6 +404,7 @@ fn test_take_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests that filter preserves relative ordering +#[expect(deprecated)] fn test_filter_preserves_order(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 4 { @@ -443,6 +450,7 @@ fn test_filter_preserves_order(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests that take with repeated indices works correctly +#[expect(deprecated)] fn test_take_repeated_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -469,6 +477,7 @@ fn test_take_repeated_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests mask and filter interaction with nulls +#[expect(deprecated)] fn test_mask_filter_null_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 3 { @@ -538,6 +547,7 @@ fn test_empty_operations_consistency(array: &ArrayRef) { } /// Tests that take preserves array properties +#[expect(deprecated)] fn test_take_preserves_properties(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -582,6 +592,7 @@ fn test_take_preserves_properties(array: &ArrayRef, ctx: &mut ExecutionCtx) { /// # Why This Matters /// Nullable indices are a powerful feature that allows introducing nulls during /// a take operation, which is useful for outer joins and similar operations. +#[expect(deprecated)] fn test_nullable_indices_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 3 { @@ -648,6 +659,7 @@ fn test_nullable_indices_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests large array consistency +#[expect(deprecated)] fn test_large_array_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 1000 { @@ -698,6 +710,7 @@ fn test_large_array_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { /// Comparison operations must maintain logical consistency across encodings. /// This test catches bugs where an encoding might implement one comparison /// correctly but fail on its logical inverse. +#[expect(deprecated)] fn test_comparison_inverse_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -818,6 +831,7 @@ fn test_comparison_inverse_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) /// # Why This Matters /// Ensures that comparison operations maintain mathematical ordering properties /// regardless of operand order. +#[expect(deprecated)] fn test_comparison_symmetry_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -909,6 +923,7 @@ fn test_comparison_symmetry_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx /// Boolean operations must maintain logical consistency across encodings. /// This test catches bugs where encodings might optimize boolean operations /// incorrectly, breaking fundamental logical properties. +#[expect(deprecated)] fn test_boolean_demorgan_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { if !matches!(array.dtype(), DType::Bool(_)) { return; @@ -1127,6 +1142,7 @@ fn widened_primitive_dtype(dtype: &DType) -> Option { /// This test specifically catches bugs where encodings (like RunEndArray) fail to preserve /// offset information during cast operations. Such bugs can lead to incorrect data being /// returned after casting a sliced array. +#[expect(deprecated)] fn test_cast_slice_consistency(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 5 { diff --git a/vortex-array/src/compute/conformance/filter.rs b/vortex-array/src/compute/conformance/filter.rs index 5c78e0d70ad..f60ae1fe949 100644 --- a/vortex-array/src/compute/conformance/filter.rs +++ b/vortex-array/src/compute/conformance/filter.rs @@ -81,6 +81,7 @@ fn test_none_filter(array: &ArrayRef) { assert_eq!(filtered.dtype(), array.dtype()); } +#[expect(deprecated)] fn test_selective_filter(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 2 { @@ -137,6 +138,7 @@ fn test_selective_filter(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_single_element_filter(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len == 0 { @@ -218,6 +220,7 @@ fn test_mismatched_lengths(array: &ArrayRef) { } /// Tests filtering with alternating true/false pattern +#[expect(deprecated)] fn test_alternating_pattern_filter(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); let pattern = create_alternating_pattern(len); diff --git a/vortex-array/src/compute/conformance/mask.rs b/vortex-array/src/compute/conformance/mask.rs index 50930ba2876..e9c70ff20cc 100644 --- a/vortex-array/src/compute/conformance/mask.rs +++ b/vortex-array/src/compute/conformance/mask.rs @@ -36,6 +36,7 @@ pub fn test_mask_conformance(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests masking with a heterogeneous pattern +#[expect(deprecated)] fn test_heterogenous_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); @@ -72,6 +73,7 @@ fn test_heterogenous_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests that an empty mask (all false) preserves all elements +#[expect(deprecated)] fn test_empty_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); let all_unmasked = vec![false; len]; @@ -98,6 +100,7 @@ fn test_empty_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests that a full mask (all true) makes all elements null +#[expect(deprecated)] fn test_full_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); let all_masked = vec![true; len]; @@ -120,6 +123,7 @@ fn test_full_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests alternating mask pattern +#[expect(deprecated)] fn test_alternating_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); let pattern: Vec = (0..len).map(|i| i % 2 == 0).collect(); @@ -153,6 +157,7 @@ fn test_alternating_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests sparse mask (only a few elements masked) +#[expect(deprecated)] fn test_sparse_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 10 { @@ -194,6 +199,7 @@ fn test_sparse_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests masking a single element +#[expect(deprecated)] fn test_single_element_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); @@ -226,6 +232,7 @@ fn test_single_element_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests double masking operations +#[expect(deprecated)] fn test_double_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); @@ -267,6 +274,7 @@ fn test_double_mask(array: &ArrayRef, ctx: &mut ExecutionCtx) { } /// Tests masking with nullable mask (nulls treated as false) +#[expect(deprecated)] fn test_nullable_mask_input(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); if len < 3 { diff --git a/vortex-array/src/compute/conformance/take.rs b/vortex-array/src/compute/conformance/take.rs index 47bc8f1bd82..3c079db3704 100644 --- a/vortex-array/src/compute/conformance/take.rs +++ b/vortex-array/src/compute/conformance/take.rs @@ -52,6 +52,7 @@ pub fn test_take_conformance(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_take_all(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); let indices = PrimitiveArray::from_iter(0..len as u64); @@ -105,6 +106,7 @@ fn test_take_none(array: &ArrayRef) { } #[expect(clippy::cast_possible_truncation)] +#[expect(deprecated)] fn test_take_selective(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); @@ -131,6 +133,7 @@ fn test_take_selective(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_take_first_and_last(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); let indices = PrimitiveArray::from_iter([0u64, (len - 1) as u64]); @@ -158,6 +161,7 @@ fn test_take_first_and_last(array: &ArrayRef, ctx: &mut ExecutionCtx) { } #[expect(clippy::cast_possible_truncation)] +#[expect(deprecated)] fn test_take_with_nullable_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); @@ -205,6 +209,7 @@ fn test_take_with_nullable_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_take_repeated_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) { if array.is_empty() { return; @@ -240,6 +245,7 @@ fn test_empty_indices(array: &ArrayRef) { assert_eq!(result.dtype(), array.dtype()); } +#[expect(deprecated)] fn test_take_reverse(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); // Take elements in reverse order @@ -263,6 +269,7 @@ fn test_take_reverse(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_take_single_middle(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); let middle_idx = len / 2; @@ -284,6 +291,7 @@ fn test_take_single_middle(array: &ArrayRef, ctx: &mut ExecutionCtx) { } #[expect(clippy::cast_possible_truncation)] +#[expect(deprecated)] fn test_take_random_unsorted(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); @@ -315,6 +323,7 @@ fn test_take_random_unsorted(array: &ArrayRef, ctx: &mut ExecutionCtx) { } } +#[expect(deprecated)] fn test_take_contiguous_range(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); let start = len / 4; @@ -342,6 +351,7 @@ fn test_take_contiguous_range(array: &ArrayRef, ctx: &mut ExecutionCtx) { } #[expect(clippy::cast_possible_truncation)] +#[expect(deprecated)] fn test_take_mixed_repeated(array: &ArrayRef, ctx: &mut ExecutionCtx) { let len = array.len(); @@ -378,6 +388,7 @@ fn test_take_mixed_repeated(array: &ArrayRef, ctx: &mut ExecutionCtx) { } #[expect(clippy::cast_possible_truncation)] +#[expect(deprecated)] fn test_take_large_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) { // Test with a large number of indices to stress test performance let len = array.len(); diff --git a/vortex-array/src/display/mod.rs b/vortex-array/src/display/mod.rs index 780140af2ac..06fd27049a8 100644 --- a/vortex-array/src/display/mod.rs +++ b/vortex-array/src/display/mod.rs @@ -523,6 +523,7 @@ impl ArrayRef { } #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] fn fmt_as(&self, f: &mut std::fmt::Formatter, options: &DisplayOptions) -> std::fmt::Result { match options { DisplayOptions::MetadataOnly => EncodingSummaryExtractor::write(self, f), diff --git a/vortex-array/src/patches.rs b/vortex-array/src/patches.rs index 641efd61404..d379e2cb1ca 100644 --- a/vortex-array/src/patches.rs +++ b/vortex-array/src/patches.rs @@ -238,6 +238,7 @@ pub struct Patches { impl Patches { #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn new( array_len: usize, offset: usize, @@ -381,6 +382,7 @@ impl Patches { #[inline] #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn chunk_offset_at(&self, idx: usize) -> VortexResult { let Some(chunk_offsets) = &self.chunk_offsets else { vortex_bail!("chunk_offsets must be set to retrieve offset at index") @@ -442,6 +444,7 @@ impl Patches { /// Get the patched value at a given index if it exists. #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn get_patched(&self, index: usize) -> VortexResult> { self.search_index(index)? .to_found() @@ -602,6 +605,7 @@ impl Patches { /// Returns the minimum patch index #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn min_index(&self) -> VortexResult { let first = self .indices @@ -614,6 +618,7 @@ impl Patches { /// Returns the maximum patch index #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn max_index(&self) -> VortexResult { let last = self .indices @@ -722,6 +727,7 @@ impl Patches { /// Slice the patches by a range of the patched array. #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn slice(&self, range: Range) -> VortexResult> { let slice_start_idx = self.search_index(range.start)?.to_index(); let slice_end_idx = self.search_index(range.end)?.to_index(); @@ -1530,6 +1536,7 @@ mod test { } #[test] + #[expect(deprecated)] fn test_mask_all_false() { let mut ctx = array_session().create_execution_ctx(); let patches = Patches::new( @@ -1630,6 +1637,7 @@ mod test { } #[test] + #[expect(deprecated)] fn test_mask_nullable_values() { let mut ctx = array_session().create_execution_ctx(); let patches = Patches::new( @@ -1843,6 +1851,7 @@ mod test { } #[test] + #[expect(deprecated)] fn test_patch_values() { let mut ctx = array_session().create_execution_ctx(); let patches = Patches::new( diff --git a/vortex-array/src/scalar_fn/fns/binary/boolean.rs b/vortex-array/src/scalar_fn/fns/binary/boolean.rs index 78f683eddf2..9bc3e518037 100644 --- a/vortex-array/src/scalar_fn/fns/binary/boolean.rs +++ b/vortex-array/src/scalar_fn/fns/binary/boolean.rs @@ -715,6 +715,7 @@ fn is_boolean_operator(operator: Operator) -> bool { #[cfg(test)] mod tests { + #![expect(deprecated)] use rstest::rstest; use vortex_error::VortexResult; diff --git a/vortex-array/src/scalar_fn/fns/binary/compare/tests.rs b/vortex-array/src/scalar_fn/fns/binary/compare/tests.rs index 40c969bf809..5e98e5e36a4 100644 --- a/vortex-array/src/scalar_fn/fns/binary/compare/tests.rs +++ b/vortex-array/src/scalar_fn/fns/binary/compare/tests.rs @@ -114,6 +114,7 @@ fn test_bool_basic_comparisons() { } #[test] +#[expect(deprecated)] fn constant_compare() { let left = ConstantArray::new(Scalar::from(2u32), 10); let right = ConstantArray::new(Scalar::from(10u32), 10); @@ -336,6 +337,7 @@ fn scalar_cmp_incompatible_extension_types_errors() { } #[test] +#[expect(deprecated)] fn test_empty_list() { let ctx = &mut array_session().create_execution_ctx(); let list = ListViewArray::new( diff --git a/vortex-array/src/scalar_fn/fns/binary/mod.rs b/vortex-array/src/scalar_fn/fns/binary/mod.rs index ed31cdf8d46..ee4b4ba07ff 100644 --- a/vortex-array/src/scalar_fn/fns/binary/mod.rs +++ b/vortex-array/src/scalar_fn/fns/binary/mod.rs @@ -466,6 +466,7 @@ mod tests { /// Regression test for GitHub issue #5947: struct comparison in filter expressions should work /// using `make_comparator` instead of Arrow's `cmp` functions which don't support nested types. #[test] + #[expect(deprecated)] fn test_struct_comparison() { use crate::IntoArray; use crate::arrays::StructArray; diff --git a/vortex-array/src/scalar_fn/fns/byte_length.rs b/vortex-array/src/scalar_fn/fns/byte_length.rs index cc709628bad..67944956b4d 100644 --- a/vortex-array/src/scalar_fn/fns/byte_length.rs +++ b/vortex-array/src/scalar_fn/fns/byte_length.rs @@ -220,6 +220,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_nullable_string_byte_length() -> VortexResult<()> { let array = VarBinArray::from_nullable_strs(vec![Some("hello"), None, Some("Пуховички")]) .into_array(); @@ -241,6 +242,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_null_scalar_byte_length() -> VortexResult<()> { let null_scalar = Scalar::null(DType::Utf8(Nullability::Nullable)); let array = ConstantArray::new(null_scalar, 2).into_array(); diff --git a/vortex-array/src/scalar_fn/fns/case_when.rs b/vortex-array/src/scalar_fn/fns/case_when.rs index b9d3450562a..fbc1d3ca8ad 100644 --- a/vortex-array/src/scalar_fn/fns/case_when.rs +++ b/vortex-array/src/scalar_fn/fns/case_when.rs @@ -380,6 +380,7 @@ fn merge_case_branches( /// Iterates spans directly, emitting one `scalar_at` per row. /// Zero per-run allocations; preferred for fragmented masks (avg run < [`SLICE_CROSSOVER_RUN_LEN`]). +#[expect(deprecated)] fn merge_row_by_row( branch_arrays: &[&ArrayRef], else_value: &ArrayRef, @@ -1219,6 +1220,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_evaluate_nary_string_output() -> VortexResult<()> { // Exercises merge_case_branches with a non-primitive (Utf8) builder. let test_array = diff --git a/vortex-array/src/scalar_fn/fns/is_not_null.rs b/vortex-array/src/scalar_fn/fns/is_not_null.rs index b6a2dcd372f..bcb0c74e31d 100644 --- a/vortex-array/src/scalar_fn/fns/is_not_null.rs +++ b/vortex-array/src/scalar_fn/fns/is_not_null.rs @@ -162,6 +162,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn evaluate_mask() { let test_array = PrimitiveArray::from_option_iter(vec![Some(1), None, Some(2), None, Some(3)]) @@ -184,6 +185,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn evaluate_all_true() { let test_array = buffer![1, 2, 3, 4, 5].into_array(); @@ -201,6 +203,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn evaluate_all_false() { let test_array = PrimitiveArray::from_option_iter(vec![None::, None, None, None, None]) @@ -220,6 +223,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn evaluate_struct() { let test_array = StructArray::from_fields(&[( "a", diff --git a/vortex-array/src/scalar_fn/fns/is_null.rs b/vortex-array/src/scalar_fn/fns/is_null.rs index 6242ad48394..8790935d910 100644 --- a/vortex-array/src/scalar_fn/fns/is_null.rs +++ b/vortex-array/src/scalar_fn/fns/is_null.rs @@ -150,6 +150,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn evaluate_mask() { let test_array = PrimitiveArray::from_option_iter(vec![Some(1), None, Some(2), None, Some(3)]) @@ -172,6 +173,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn evaluate_all_false() { let test_array = buffer![1, 2, 3, 4, 5].into_array(); @@ -190,6 +192,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn evaluate_all_true() { let test_array = PrimitiveArray::from_option_iter(vec![None::, None, None, None, None]) @@ -210,6 +213,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn evaluate_struct() { let test_array = StructArray::from_fields(&[( "a", diff --git a/vortex-array/src/scalar_fn/fns/list_contains/mod.rs b/vortex-array/src/scalar_fn/fns/list_contains/mod.rs index d2508014089..ae5e2c85e31 100644 --- a/vortex-array/src/scalar_fn/fns/list_contains/mod.rs +++ b/vortex-array/src/scalar_fn/fns/list_contains/mod.rs @@ -225,6 +225,7 @@ fn constant_list_scalar_contains( } /// Returns a [`BoolArray`] where each bit represents if a list contains the scalar. +#[expect(deprecated)] fn list_contains_scalar( array: &ArrayRef, value: &Scalar, @@ -459,6 +460,7 @@ mod tests { } #[test] + #[expect(deprecated)] pub fn test_one() { let arr = test_array(); @@ -478,6 +480,7 @@ mod tests { } #[test] + #[expect(deprecated)] pub fn test_all() { let arr = test_array(); @@ -497,6 +500,7 @@ mod tests { } #[test] + #[expect(deprecated)] pub fn test_none() { let arr = test_array(); @@ -516,6 +520,7 @@ mod tests { } #[test] + #[expect(deprecated)] pub fn test_empty() { let arr = ListArray::try_new( PrimitiveArray::from_iter(vec![1, 1, 2, 2, 2]).into_array(), @@ -541,6 +546,7 @@ mod tests { } #[test] + #[expect(deprecated)] pub fn test_nullable() { let arr = ListArray::try_new( PrimitiveArray::from_iter(vec![1, 1, 2, 2, 2]).into_array(), @@ -640,6 +646,7 @@ mod tests { } #[test] + #[expect(deprecated)] pub fn test_constant_scalars() { let arr = test_array(); diff --git a/vortex-array/src/scalar_fn/fns/list_length.rs b/vortex-array/src/scalar_fn/fns/list_length.rs index 058cefd1c16..8a3369901a8 100644 --- a/vortex-array/src/scalar_fn/fns/list_length.rs +++ b/vortex-array/src/scalar_fn/fns/list_length.rs @@ -267,6 +267,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_null_scalar_list_length() -> VortexResult<()> { let null_scalar = Scalar::null(DType::List( Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)), diff --git a/vortex-array/src/scalar_fn/fns/list_sum.rs b/vortex-array/src/scalar_fn/fns/list_sum.rs index f73551250c3..6e09f38d6d8 100644 --- a/vortex-array/src/scalar_fn/fns/list_sum.rs +++ b/vortex-array/src/scalar_fn/fns/list_sum.rs @@ -79,6 +79,7 @@ impl ScalarFnVTable for ListSum { .ok_or_else(|| vortex_err!("list_sum() cannot sum elements of type {elem_dtype}")) } + #[expect(deprecated)] fn execute( &self, options: &Self::Options, @@ -322,6 +323,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_nan_poisons_with_include_nans() -> VortexResult<()> { let elements = PrimitiveArray::from_iter([1.0f64, f64::NAN, 2.0]); let list = ListArray::try_new( @@ -448,6 +450,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_constant_list_sum() -> VortexResult<()> { let elements = create_list_elements(); let list = ListArray::try_new( @@ -467,6 +470,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_null_scalar_list_sum() -> VortexResult<()> { let null_scalar = Scalar::null(DType::List( Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)), diff --git a/vortex-array/src/scalar_fn/fns/variant_get/mod.rs b/vortex-array/src/scalar_fn/fns/variant_get/mod.rs index 2634aff1d82..95c3ebef48b 100644 --- a/vortex-array/src/scalar_fn/fns/variant_get/mod.rs +++ b/vortex-array/src/scalar_fn/fns/variant_get/mod.rs @@ -131,6 +131,7 @@ impl ScalarFnVTable for VariantGet { .map_or(DType::Variant(Nullability::Nullable), DType::as_nullable)) } + #[expect(deprecated)] fn execute( &self, options: &Self::Options, @@ -772,6 +773,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn variant_get_generic_fallback_preserves_variant_null() -> VortexResult<()> { let array = variant_rows([ Scalar::variant(variant_object([( @@ -810,6 +812,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn variant_get_fallback_variant_output_canonicalizes() -> VortexResult<()> { let array = variant_rows([ Scalar::variant(variant_object([( diff --git a/vortex-array/src/scalar_fn/unstable/row/batch/execute/constant.rs b/vortex-array/src/scalar_fn/unstable/row/batch/execute/constant.rs index 0cd60e821b6..d3e5dd5f168 100644 --- a/vortex-array/src/scalar_fn/unstable/row/batch/execute/constant.rs +++ b/vortex-array/src/scalar_fn/unstable/row/batch/execute/constant.rs @@ -13,6 +13,7 @@ use crate::arrays::ConstantArray; impl RowFnExecutionArgs { /// Execute all-constant inputs by evaluating one row and broadcasting the validated result. + #[expect(deprecated)] pub(super) fn execute_all_constant( &self, kernel: impl Fn(BorrowedRowFnArgs<'_>, &mut ExecutionCtx) -> VortexResult, diff --git a/vortex-array/src/search_sorted/mod.rs b/vortex-array/src/search_sorted/mod.rs index cda27cfc511..e479e023998 100644 --- a/vortex-array/src/search_sorted/mod.rs +++ b/vortex-array/src/search_sorted/mod.rs @@ -274,6 +274,7 @@ fn search_sorted_side_idx VortexResult>( impl IndexOrd for ArrayRef { #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] fn index_cmp(&self, idx: usize, elem: &Scalar) -> VortexResult> { let scalar_a = self.execute_scalar(idx, &mut legacy_session().create_execution_ctx())?; Ok(scalar_a.partial_cmp(elem)) diff --git a/vortex-array/src/search_sorted/primitive.rs b/vortex-array/src/search_sorted/primitive.rs index 3da28c7be2b..918703ad924 100644 --- a/vortex-array/src/search_sorted/primitive.rs +++ b/vortex-array/src/search_sorted/primitive.rs @@ -37,6 +37,7 @@ impl<'a, T: NativePType> SearchSortedPrimitiveArray<'a, T> { } /// Returns the value at `idx`, with nulls mapped to `T::zero()`. + #[expect(deprecated)] fn value(&self, idx: usize) -> VortexResult { Ok(self .0 @@ -59,6 +60,7 @@ impl IndexOrd for SearchSortedPrimitiveArray<'_, T> { } impl IndexOrd> for SearchSortedPrimitiveArray<'_, T> { + #[expect(deprecated)] fn index_cmp(&self, idx: usize, elem: &Option) -> VortexResult> { // The borrow must end before `self.value` re-borrows the ctx. let valid = self.0.is_valid(idx, &mut self.1.borrow_mut())?; diff --git a/vortex-array/src/validity.rs b/vortex-array/src/validity.rs index a2c10e30ca7..15d62197391 100644 --- a/vortex-array/src/validity.rs +++ b/vortex-array/src/validity.rs @@ -164,6 +164,7 @@ impl Validity { /// Returns whether the `index` item is valid, using `ctx` to execute the validity array. #[inline] + #[expect(deprecated)] pub fn execute_is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { Ok(match self { Self::NonNullable | Self::AllValid => true, diff --git a/vortex-arrow/src/datum.rs b/vortex-arrow/src/datum.rs index 953a9d8d714..cb2e826230f 100644 --- a/vortex-arrow/src/datum.rs +++ b/vortex-arrow/src/datum.rs @@ -150,6 +150,7 @@ where /// # Error /// /// The provided array must have length `len` or `1`. +#[expect(deprecated)] pub fn from_arrow_columnar( array: ArrowArrayRef, len: usize, diff --git a/vortex-btrblocks/src/schemes/patches.rs b/vortex-btrblocks/src/schemes/patches.rs index 69ca8450f12..dbf829b725a 100644 --- a/vortex-btrblocks/src/schemes/patches.rs +++ b/vortex-btrblocks/src/schemes/patches.rs @@ -12,6 +12,7 @@ use vortex_error::VortexError; use vortex_error::VortexResult; /// Compresses the given patches by downscaling integers and checking for constant values. +#[expect(deprecated)] pub fn compress_patches(patches: Patches, ctx: &mut ExecutionCtx) -> VortexResult { // Downscale the patch indices. let indices = patches diff --git a/vortex-compressor/src/compressor/constant.rs b/vortex-compressor/src/compressor/constant.rs index 7e8edc73f2f..0848a17540c 100644 --- a/vortex-compressor/src/compressor/constant.rs +++ b/vortex-compressor/src/compressor/constant.rs @@ -97,6 +97,7 @@ pub(crate) fn is_constant_for_compression( /// # Errors /// /// Returns an error if computing validity or extracting the constant scalar fails. +#[expect(deprecated)] pub(crate) fn compress_constant( source: &ArrayRef, ctx: &mut ExecutionCtx, diff --git a/vortex-datafusion/src/convert/scalars.rs b/vortex-datafusion/src/convert/scalars.rs index c2b1dfc79b2..91f5df3c2aa 100644 --- a/vortex-datafusion/src/convert/scalars.rs +++ b/vortex-datafusion/src/convert/scalars.rs @@ -365,6 +365,7 @@ fn struct_to_df(scalar: &Scalar) -> VortexResult { /// same fields. Going through `ScalarValue` instead would drop each field's `ARROW:extension:name` /// (and its declared nullability), yielding storage-typed children that /// [`Scalar::struct_`] rejects against an extension-typed struct dtype. +#[expect(deprecated)] fn struct_from_df(array: &StructArray, session: &VortexSession) -> Scalar { let arrow = session.arrow(); let dtype = arrow diff --git a/vortex-duckdb/src/exporter/run_end.rs b/vortex-duckdb/src/exporter/run_end.rs index f252866ef3c..a70288cb729 100644 --- a/vortex-duckdb/src/exporter/run_end.rs +++ b/vortex-duckdb/src/exporter/run_end.rs @@ -94,6 +94,7 @@ impl ColumnExporter for RunEndExporter { if start_run_idx == end_run_idx { // NOTE(ngates): would be great if we could just export and set type == CONSTANT // self.values_exporter.export(start_run_idx, 1, vector, cache); + #[expect(deprecated)] let constant = self.values.execute_scalar(start_run_idx, ctx)?; let value = constant.try_to_duckdb_scalar()?; vector.reference_value(&value); diff --git a/vortex-ffi/src/array.rs b/vortex-ffi/src/array.rs index 3d4b9758567..6b482b117a5 100644 --- a/vortex-ffi/src/array.rs +++ b/vortex-ffi/src/array.rs @@ -263,6 +263,7 @@ pub unsafe extern "C-unwind" fn vx_array_slice( /// array is corrupted. #[unsafe(no_mangle)] #[allow(clippy::disallowed_methods)] +#[expect(deprecated)] pub unsafe extern "C-unwind" fn vx_array_element_is_invalid( session: *const vx_session, array: *const vx_array, @@ -516,6 +517,7 @@ pub unsafe extern "C-unwind" fn vx_array_get_bool(array: *const vx_array, index: /// /// Errors if "index" is out of bounds. #[unsafe(no_mangle)] +#[expect(deprecated)] pub unsafe extern "C-unwind" fn vx_array_get_scalar( session: *const vx_session, array: *const vx_array, diff --git a/vortex-ffi/src/expression.rs b/vortex-ffi/src/expression.rs index 1c3eaad3b72..9e56f6d690e 100644 --- a/vortex-ffi/src/expression.rs +++ b/vortex-ffi/src/expression.rs @@ -419,6 +419,7 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] + #[expect(deprecated)] fn test_literal() { let mut ctx = array_session().create_execution_ctx(); let array = diff --git a/vortex-file/src/pruning.rs b/vortex-file/src/pruning.rs index df327638d00..1e9aeb0b4b5 100644 --- a/vortex-file/src/pruning.rs +++ b/vortex-file/src/pruning.rs @@ -25,6 +25,7 @@ use vortex_session::VortexSession; use crate::FileStatistics; +#[expect(deprecated)] pub(crate) fn can_prune_file_stats( expr: &BoundExpression, row_count: u64, diff --git a/vortex-layout/src/layouts/list/reader.rs b/vortex-layout/src/layouts/list/reader.rs index 53227635de6..b58a4de6957 100644 --- a/vortex-layout/src/layouts/list/reader.rs +++ b/vortex-layout/src/layouts/list/reader.rs @@ -543,6 +543,7 @@ fn fetch_validity( } /// Read `offsets[0]` and `offsets[-1]` and return the elements range they bound. +#[expect(deprecated)] fn elements_range_from_offsets( offsets: &ArrayRef, session: &VortexSession, diff --git a/vortex-layout/src/layouts/struct_/reader.rs b/vortex-layout/src/layouts/struct_/reader.rs index 005a4e47fed..9bf26ff254a 100644 --- a/vortex-layout/src/layouts/struct_/reader.rs +++ b/vortex-layout/src/layouts/struct_/reader.rs @@ -877,6 +877,7 @@ mod tests { } #[rstest] + #[expect(deprecated)] fn test_struct_layout_nulls( #[from(null_struct_layout)] (segments, layout): (Arc, LayoutRef), ) { @@ -909,6 +910,7 @@ mod tests { } #[rstest] + #[expect(deprecated)] fn test_struct_layout_nested( #[from(nested_struct_layout)] (segments, layout): (Arc, LayoutRef), ) { diff --git a/vortex-python/src/arrays/mod.rs b/vortex-python/src/arrays/mod.rs index 834b42ef718..6731f1dfe2e 100644 --- a/vortex-python/src/arrays/mod.rs +++ b/vortex-python/src/arrays/mod.rs @@ -799,6 +799,7 @@ impl PyArray { /// OverflowError: can't convert negative int to unsigned /// ``` // TODO(ngates): return a vortex.Scalar + #[expect(deprecated)] fn scalar_at(slf: Bound, index: usize) -> PyVortexResult> { // PyArray/PyArrayRef do not currently carry a VortexSession; threading one // through would change the FromPyObject contract. Use the crate session diff --git a/vortex-python/src/scan.rs b/vortex-python/src/scan.rs index 913443cc298..24f9c261063 100644 --- a/vortex-python/src/scan.rs +++ b/vortex-python/src/scan.rs @@ -59,6 +59,7 @@ impl PyRepeatedScan { }) } + #[expect(deprecated)] fn scalar_at(slf: Bound, index: u64) -> PyVortexResult> { let row_count = slf.get().row_count; if index >= row_count { diff --git a/vortex-spatial/benches/binary_predicates.rs b/vortex-spatial/benches/binary_predicates.rs index 8cc25888e59..77e032dddf2 100644 --- a/vortex-spatial/benches/binary_predicates.rs +++ b/vortex-spatial/benches/binary_predicates.rs @@ -83,6 +83,7 @@ fn query_ring(cx: f64, cy: f64) -> Vec<(f64, f64)> { /// The query polygon as a batch-constant operand: a top-level `ConstantArray` over the geometry /// extension scalar, the shape that reaches the row loop's stride-0 path. +#[expect(deprecated)] fn query_constant(ctx: &mut ExecutionCtx, rows: usize) -> ArrayRef { let scalar = polygon_column(vec![vec![query_ring(0.0, 0.0)]]) .unwrap() @@ -92,6 +93,7 @@ fn query_constant(ctx: &mut ExecutionCtx, rows: usize) -> ArrayRef { } /// A batch-constant point operand with the requested row count. +#[expect(deprecated)] fn point_constant(ctx: &mut ExecutionCtx, rows: usize) -> ArrayRef { let scalar = point_column(vec![0.0], vec![0.0]) .unwrap() diff --git a/vortex-spatial/benches/distance.rs b/vortex-spatial/benches/distance.rs index 50714841e1e..32a3b53cbf6 100644 --- a/vortex-spatial/benches/distance.rs +++ b/vortex-spatial/benches/distance.rs @@ -77,6 +77,7 @@ fn polygons(rows: usize) -> ArrayRef { polygon_column(rows).unwrap() } +#[expect(deprecated)] fn point_constant(x: f64, y: f64, rows: usize, ctx: &mut ExecutionCtx) -> ArrayRef { let scalar = point_column(vec![x], vec![y]) .unwrap() @@ -85,6 +86,7 @@ fn point_constant(x: f64, y: f64, rows: usize, ctx: &mut ExecutionCtx) -> ArrayR ConstantArray::new(scalar, rows).into_array() } +#[expect(deprecated)] fn polygon_constant(rows: usize, ctx: &mut ExecutionCtx) -> ArrayRef { let scalar = polygon_column(vec![square(0.0, 0.0)]) .unwrap() diff --git a/vortex-spatial/benches/make_line.rs b/vortex-spatial/benches/make_line.rs index 04cb2c47505..476b5c3ca10 100644 --- a/vortex-spatial/benches/make_line.rs +++ b/vortex-spatial/benches/make_line.rs @@ -74,6 +74,7 @@ fn nullable_points(offset: usize, null_every: usize) -> ArrayRef { .unwrap() } +#[expect(deprecated)] fn point_constant(ctx: &mut ExecutionCtx) -> ArrayRef { let scalar = point_column(vec![0.0], vec![0.0]) .unwrap() diff --git a/vortex-spatial/src/extension/point.rs b/vortex-spatial/src/extension/point.rs index e6a00fe8fea..f8cae058e3a 100644 --- a/vortex-spatial/src/extension/point.rs +++ b/vortex-spatial/src/extension/point.rs @@ -348,6 +348,7 @@ mod tests { /// A `Point` column round-trips through scalar execution back to the original coordinates. #[test] + #[expect(deprecated)] fn point_unpacks_coordinates() -> VortexResult<()> { let session = vortex_array::array_session(); let mut ctx = session.create_execution_ctx(); diff --git a/vortex-spatial/src/prune/distance.rs b/vortex-spatial/src/prune/distance.rs index 9231b33875e..e3cdd630b10 100644 --- a/vortex-spatial/src/prune/distance.rs +++ b/vortex-spatial/src/prune/distance.rs @@ -154,6 +154,7 @@ mod tests { /// Run the rule against `SpatialDistance(root, origin) radius`, operands swapped when /// `geom_first` is false. The radius is any literal scalar, matching the uncoerced filter /// expressions the rule sees in production. + #[expect(deprecated)] fn falsify_distance( operator: Operator, geom_first: bool, @@ -253,6 +254,7 @@ mod tests { /// An extension-typed radius passes `Binary`'s typecheck (extension operands are exempt) but /// has no numeric value - the rule declines rather than erroring, and the chunk is scanned. #[test] + #[expect(deprecated)] fn extension_radius_never_prunes() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); @@ -264,6 +266,7 @@ mod tests { /// A non-geometry scope is rejected while binding, before stats rewriting. #[test] + #[expect(deprecated)] fn unsupported_scope_is_not_pruned() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); @@ -290,6 +293,7 @@ mod tests { /// End-to-end over a hand-built zone map: the far chunk is skipped, the near one kept. #[test] + #[expect(deprecated)] fn prunes_far_chunk_keeps_near() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); @@ -318,6 +322,7 @@ mod tests { /// The true-distance prune skips a chunk that is *diagonally* farther than `r`, even though /// neither axis alone exceeds `r`, the case a per-axis box-overlap test would wrongly keep. #[test] + #[expect(deprecated)] fn prunes_diagonally_distant_chunk() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); @@ -348,6 +353,7 @@ mod tests { /// A `>= r` filter prunes a chunk lying wholly *within* `r` (every row nearer than `r`, so none /// satisfy `>= r`) via the box max-distance, while a chunk beyond `r` is kept. #[test] + #[expect(deprecated)] fn prunes_within_chunk_for_far_filter() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); @@ -376,6 +382,7 @@ mod tests { /// Backward compat: a zone map written without the `GeometryAabb` stat (an older file) keeps /// every zone, the missing stat binds to null and `null_as_false` retains the zone. #[test] + #[expect(deprecated)] fn missing_aabb_stat_keeps_all_zones() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); diff --git a/vortex-spatial/src/prune/intersects.rs b/vortex-spatial/src/prune/intersects.rs index 74103003b2f..c916c178783 100644 --- a/vortex-spatial/src/prune/intersects.rs +++ b/vortex-spatial/src/prune/intersects.rs @@ -75,6 +75,7 @@ mod tests { /// Run the intersects rule against `SpatialIntersects(root, point(1.0, 0.5))`, operands swapped /// when `geom_first` is false. + #[expect(deprecated)] fn falsify_intersects(geom_first: bool) -> VortexResult> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); @@ -103,6 +104,7 @@ mod tests { /// A non-geometry scope is rejected while binding, before stats rewriting. #[test] + #[expect(deprecated)] fn unsupported_scope_is_not_pruned() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); @@ -134,6 +136,7 @@ mod tests { /// End-to-end: a zone strictly separated from the query is skipped; zones containing or merely /// touching the query must scan, touching geometries intersect under OGC semantics. #[test] + #[expect(deprecated)] fn prunes_disjoint_keeps_touching_and_containing() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); @@ -164,6 +167,7 @@ mod tests { /// Backward compat: a zone map written without the `GeometryAabb` stat keeps every zone. #[test] + #[expect(deprecated)] fn missing_aabb_stat_keeps_all_zones() -> VortexResult<()> { let session = spatial_session(); let mut ctx = session.create_execution_ctx(); diff --git a/vortex-spatial/src/scalar_fn/collect.rs b/vortex-spatial/src/scalar_fn/collect.rs index a7708f8e8d3..faef46fdd9b 100644 --- a/vortex-spatial/src/scalar_fn/collect.rs +++ b/vortex-spatial/src/scalar_fn/collect.rs @@ -210,6 +210,7 @@ fn collect_list_rows( } /// Apply [`collect_list_rows`] to a constant or column, after shared unary null dispatch. +#[expect(deprecated)] fn execute_collect( execution: Execution<1, Validity>, output_dtype: &ExtDTypeRef, @@ -504,6 +505,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn constant_list_remains_constant() -> VortexResult<()> { let input = list( nullable_point_column(vec![Some((0.0, 2.0)), None, Some((1.0, 3.0))])?, diff --git a/vortex-spatial/src/scalar_fn/convex_hull.rs b/vortex-spatial/src/scalar_fn/convex_hull.rs index f7bc47f891c..e371949c2fe 100644 --- a/vortex-spatial/src/scalar_fn/convex_hull.rs +++ b/vortex-spatial/src/scalar_fn/convex_hull.rs @@ -199,6 +199,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn constant_remains_constant() -> VortexResult<()> { let mut ctx = vortex_array::array_session().create_execution_ctx(); let scalar = multipoint_column(vec![vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]])? diff --git a/vortex-spatial/src/scalar_fn/distance.rs b/vortex-spatial/src/scalar_fn/distance.rs index c78c8f89262..749bf7aee7f 100644 --- a/vortex-spatial/src/scalar_fn/distance.rs +++ b/vortex-spatial/src/scalar_fn/distance.rs @@ -95,6 +95,7 @@ mod tests { use crate::test_harness::polygon_column; /// A constant `Point` column of length `len`, every row at `(x, y)`. + #[expect(deprecated)] fn point_constant( x: f64, y: f64, @@ -146,6 +147,7 @@ mod tests { /// Distance passes no bounding-rect rejection: a point far outside a constant polygon's /// bounding rect still gets its true distance, alongside an inside point at distance zero. #[test] + #[expect(deprecated)] fn distance_to_constant_polygon_is_exact() -> VortexResult<()> { let session = vortex_array::array_session(); let mut ctx = session.create_execution_ctx(); diff --git a/vortex-spatial/src/scalar_fn/envelope.rs b/vortex-spatial/src/scalar_fn/envelope.rs index 553fb415017..c76a505084f 100644 --- a/vortex-spatial/src/scalar_fn/envelope.rs +++ b/vortex-spatial/src/scalar_fn/envelope.rs @@ -188,6 +188,7 @@ fn envelope_array( } /// Execute `envelope` after shared constant/column and null dispatch. +#[expect(deprecated)] fn execute_envelope( execution: Execution<1, Validity>, output_dtype: &ExtDType, @@ -548,6 +549,7 @@ mod tests { /// A non-null constant is boxed once and retained as a constant output. #[test] + #[expect(deprecated)] fn constant_point_remains_constant() -> VortexResult<()> { let session = crate::test_harness::spatial_session(); let mut ctx = session.create_execution_ctx(); diff --git a/vortex-spatial/src/scalar_fn/length.rs b/vortex-spatial/src/scalar_fn/length.rs index f2c12b72692..3d5d24242cc 100644 --- a/vortex-spatial/src/scalar_fn/length.rs +++ b/vortex-spatial/src/scalar_fn/length.rs @@ -130,6 +130,7 @@ fn length_array( } /// Execute length after shared constant/column and null dispatch. +#[expect(deprecated)] fn execute_length( execution: Execution<1, Validity>, ctx: &mut ExecutionCtx, @@ -248,6 +249,7 @@ mod tests { use crate::test_harness::multilinestring_column; use crate::test_harness::point_column; + #[expect(deprecated)] fn line_constant( line: Vec<(f64, f64)>, len: usize, diff --git a/vortex-spatial/src/scalar_fn/make_line.rs b/vortex-spatial/src/scalar_fn/make_line.rs index fdd69676acd..278c976c338 100644 --- a/vortex-spatial/src/scalar_fn/make_line.rs +++ b/vortex-spatial/src/scalar_fn/make_line.rs @@ -136,6 +136,7 @@ fn build_make_lines( } /// Execute `ST_MakeLine` after shared constant/column and null dispatch. +#[expect(deprecated)] fn execute_make_line( execution: Execution<2>, output_dtype: &ExtDType, @@ -299,6 +300,7 @@ mod tests { Ok(ExtensionArray::try_new(dtype.erased(), storage)?.into_array()) } + #[expect(deprecated)] fn point_constant( x: f64, y: f64, diff --git a/vortex-spatial/src/tests/point.rs b/vortex-spatial/src/tests/point.rs index aec2e91bcbf..338d56615bc 100644 --- a/vortex-spatial/src/tests/point.rs +++ b/vortex-spatial/src/tests/point.rs @@ -135,6 +135,7 @@ fn import_interleaved_field_fails() { /// Import wraps the Arrow struct's coordinate buffers into a Point column. #[test] +#[expect(deprecated)] fn imports_from_struct() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let arrow: ArrowArrayRef = @@ -163,6 +164,7 @@ fn imports_from_struct() -> VortexResult<()> { /// A point column exported to Arrow and imported back is unchanged, including the CRS. #[test] +#[expect(deprecated)] fn roundtrips_through_arrow() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let original = point_column(vec![0.0, 3.0], vec![4.0, 0.0])?; diff --git a/vortex-spatial/src/tests/rect.rs b/vortex-spatial/src/tests/rect.rs index 9baf724af6e..4860f781e83 100644 --- a/vortex-spatial/src/tests/rect.rs +++ b/vortex-spatial/src/tests/rect.rs @@ -73,6 +73,7 @@ fn import_field_recovers_extension() -> VortexResult<()> { /// A `Rect` column exported to Arrow and imported back is unchanged, including the CRS and the /// box corners. #[test] +#[expect(deprecated)] fn roundtrips_through_arrow() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let original = rect_column(vec![(0.0, 1.0, 2.0, 3.0), (-5.0, -5.0, 5.0, 5.0)])?; diff --git a/vortex-spatial/src/tests/wkb.rs b/vortex-spatial/src/tests/wkb.rs index 5ed4273cb0b..d4b197f3c4f 100644 --- a/vortex-spatial/src/tests/wkb.rs +++ b/vortex-spatial/src/tests/wkb.rs @@ -103,6 +103,7 @@ fn assert_imported_wkb_dtype(dtype: &DType, expected_crs: Option<&str>, nullable /// WKB scalars unpack back to the geometry they encode. #[test] +#[expect(deprecated)] fn scalar_unpacks_to_geometry() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let (_, array) = wkb_extension_array()?; @@ -182,6 +183,7 @@ fn import_field_without_crs() -> VortexResult<()> { /// Import wraps the binary-family Arrow array's WKB values unchanged. #[test] +#[expect(deprecated)] fn imports_from_binary() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let (wkb_bytes, _) = wkb_extension_array()?; @@ -238,6 +240,7 @@ fn imports_from_binary_view() -> VortexResult<()> { /// A WKB column exported to Arrow and imported back is unchanged, byte for byte. #[test] +#[expect(deprecated)] fn roundtrips_through_arrow() -> VortexResult<()> { let mut ctx = SESSION.create_execution_ctx(); let (wkb_bytes, original) = wkb_extension_array()?; diff --git a/vortex-tensor/src/scalar_fns/cosine_similarity.rs b/vortex-tensor/src/scalar_fns/cosine_similarity.rs index 5ffc466bfa8..ca4f2b3437e 100644 --- a/vortex-tensor/src/scalar_fns/cosine_similarity.rs +++ b/vortex-tensor/src/scalar_fns/cosine_similarity.rs @@ -365,6 +365,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn null_input_row() -> VortexResult<()> { // 2 rows of dim-2 vectors. Row 1 of rhs is masked as null. let lhs = tensor_array(&[2], &[3.0, 4.0, 1.0, 0.0])?; diff --git a/vortex-tensor/src/scalar_fns/inner_product.rs b/vortex-tensor/src/scalar_fns/inner_product.rs index 974b59e7e91..bf9739b69d2 100644 --- a/vortex-tensor/src/scalar_fns/inner_product.rs +++ b/vortex-tensor/src/scalar_fns/inner_product.rs @@ -290,6 +290,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn null_input_row() -> VortexResult<()> { // 3 rows of dim-2 vectors. Row 1 of lhs is masked as null. let lhs = tensor_array(&[2], &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0])?; diff --git a/vortex-tensor/src/scalar_fns/l2_norm.rs b/vortex-tensor/src/scalar_fns/l2_norm.rs index ea7b47a721e..086da54d2d5 100644 --- a/vortex-tensor/src/scalar_fns/l2_norm.rs +++ b/vortex-tensor/src/scalar_fns/l2_norm.rs @@ -306,6 +306,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn null_input_row() -> VortexResult<()> { // 2 rows of dim-2 vectors. Row 1 is masked as null. let arr = tensor_array(&[2], &[3.0, 4.0, 0.0, 0.0])?; diff --git a/vortex-tensor/src/scalar_fns/l2_normalize.rs b/vortex-tensor/src/scalar_fns/l2_normalize.rs index 74139a506a2..7dabb8cbf19 100644 --- a/vortex-tensor/src/scalar_fns/l2_normalize.rs +++ b/vortex-tensor/src/scalar_fns/l2_normalize.rs @@ -373,6 +373,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_input_nulls_produce_null_struct_rows() -> VortexResult<()> { let input = vector_array( 2, diff --git a/vortex-test/compat-gen/src/fixtures/arrays/synthetic/encodings/constant.rs b/vortex-test/compat-gen/src/fixtures/arrays/synthetic/encodings/constant.rs index c77c10fe311..409718046d9 100644 --- a/vortex-test/compat-gen/src/fixtures/arrays/synthetic/encodings/constant.rs +++ b/vortex-test/compat-gen/src/fixtures/arrays/synthetic/encodings/constant.rs @@ -39,6 +39,7 @@ impl FlatLayoutFixture for ConstantFixture { vec![Constant.id()] } + #[expect(deprecated)] fn build(&self, ctx: &mut ExecutionCtx) -> VortexResult { let const_i32 = ConstantArray::new(42i32, N); let const_f64 = ConstantArray::new(99.99f64, N); diff --git a/vortex-tui/src/browse/ui/layouts.rs b/vortex-tui/src/browse/ui/layouts.rs index bace4ef2c0e..1aba5737aa0 100644 --- a/vortex-tui/src/browse/ui/layouts.rs +++ b/vortex-tui/src/browse/ui/layouts.rs @@ -125,6 +125,7 @@ fn render_layout_header(app: &AppState, area: Rect, buf: &mut Buffer) { } /// Render the inner Array for a FlatLayout. +#[expect(deprecated)] fn render_array(app: &AppState, area: Rect, buf: &mut Buffer, is_stats_table: bool) { // Array data is loaded eagerly when navigating to a FlatLayout (synchronously on // native, asynchronously on WASM) and cached in AppState. The render loop never From 54a5a54fa93223bf4ad8e8776922b6cf642294c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 21:39:40 +0000 Subject: [PATCH 7/9] feat(array): add a validity probe mirroring the array probe `ArrayRef::probe` retains preparation across scalar lookups, but `Validity` had no equivalent: its per-row accessors execute the validity array from scratch on every call, and the only `ValidityProbe` a caller could reach came from `ProbeAccess::validity` inside an encoding's `probe_scalar`. Add `Validity::probe`, taking the same `ProbeUsage` as the array probe and returning an owned `ValidityProbe`. Constant states answer from the variant alone; `Validity::Array` reads through an owned `ArrayProbe`, so `Repeated` keeps its preparation instead of rebuilding it per row. `ValidityProbe` gains an `Owned` access variant alongside `Once` and `Repeated`, and `execute_is_valid`/`execute_is_invalid` next to the existing `execute_scalar`. Its `len` becomes optional: a probe borrowed from a parent still bounds-checks against the source array, while one built from a bare `Validity` has no length of its own, so constant states answer any index and array-backed validity is bounds-checked by its own probe. Signed-off-by: "Joe Isaacs" Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMRyEJHyh72KkM8o9paBeP --- vortex-array/src/array/probe.rs | 41 +++++++++++++++++++++++++++++---- vortex-array/src/validity.rs | 27 ++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/vortex-array/src/array/probe.rs b/vortex-array/src/array/probe.rs index 5ef729d5d81..c8efd158216 100644 --- a/vortex-array/src/array/probe.rs +++ b/vortex-array/src/array/probe.rs @@ -238,28 +238,43 @@ impl ArrayProbe { } /// A validity accessor with the parent probe's retention policy. +/// +/// A probe borrowed from a parent knows the source array's length and checks bounds against it. +/// One built from a bare [`Validity`] has no length of its own: constant states answer every +/// index, and array-backed validity is bounds-checked by its own probe. pub struct ValidityProbe<'p> { - len: usize, + len: Option, inner: ValidityAccess<'p>, } enum ValidityAccess<'p> { Once(Validity), Repeated(&'p mut ProbeValidity), + Owned(ProbeValidity), } impl ValidityProbe<'_> { pub(super) fn once(array: &ArrayRef) -> VortexResult { Ok(Self { - len: array.len(), + len: Some(array.len()), inner: ValidityAccess::Once(array.validity()?), }) } + /// Own a [`Validity`] and choose whether to retain preparation between lookups. + pub(crate) fn owned(validity: Validity, usage: ProbeUsage) -> Self { + Self { + len: None, + inner: ValidityAccess::Owned(ProbeValidity::new(validity, usage)), + } + } + /// Read a non-null boolean scalar indicating whether the requested row is valid. #[expect(deprecated)] pub fn execute_scalar(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { - vortex_ensure!(index < self.len, OutOfBounds: index, 0, self.len); + if let Some(len) = self.len { + vortex_ensure!(index < len, OutOfBounds: index, 0, len); + } match &mut self.inner { ValidityAccess::Once(validity) => match validity { Validity::NonNullable | Validity::AllValid => Ok(true.into()), @@ -267,8 +282,26 @@ impl ValidityProbe<'_> { Validity::Array(array) => array.execute_scalar(index, ctx), }, ValidityAccess::Repeated(validity) => validity.execute_scalar(index, ctx), + ValidityAccess::Owned(validity) => validity.execute_scalar(index, ctx), } } + + /// Returns whether the item at `index` is valid. + pub fn execute_is_valid(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { + self.execute_scalar(index, ctx)? + .as_bool() + .value() + .ok_or_else(|| vortex_err!("validity value at index {index} is null")) + } + + /// Returns whether the item at `index` is invalid. + pub fn execute_is_invalid( + &mut self, + index: usize, + ctx: &mut ExecutionCtx, + ) -> VortexResult { + Ok(!self.execute_is_valid(index, ctx)?) + } } fn validity_access<'p>( @@ -280,7 +313,7 @@ fn validity_access<'p>( slot @ None => slot.insert(ProbeValidity::new(array.validity()?, ProbeUsage::Repeated)), }; Ok(ValidityProbe { - len: array.len(), + len: Some(array.len()), inner: ValidityAccess::Repeated(validity), }) } diff --git a/vortex-array/src/validity.rs b/vortex-array/src/validity.rs index 15d62197391..1ba84e6f3b1 100644 --- a/vortex-array/src/validity.rs +++ b/vortex-array/src/validity.rs @@ -24,6 +24,8 @@ use crate::ArrayRef; use crate::Canonical; use crate::ExecutionCtx; use crate::IntoArray; +use crate::ProbeUsage; +use crate::ValidityProbe; use crate::VortexSessionExecute; use crate::arrays::BoolArray; use crate::arrays::ChunkedArray; @@ -162,6 +164,31 @@ impl Validity { } } + /// Create an accessor with the requested policy for retaining state between validity lookups. + /// + /// Mirrors [`ArrayRef::probe`]. Constant states answer from the variant alone; + /// [`Validity::Array`] reads through an owned [`ArrayProbe`](crate::ArrayProbe), so + /// [`ProbeUsage::Repeated`] keeps its preparation across lookups instead of rebuilding it + /// for every row. + /// + /// ``` + /// use vortex_array::IntoArray; + /// use vortex_array::ProbeUsage; + /// use vortex_array::VortexSessionExecute; + /// use vortex_array::arrays::PrimitiveArray; + /// + /// let array = PrimitiveArray::from_option_iter([Some(10i32), None, Some(30)]).into_array(); + /// let mut ctx = vortex_array::array_session().create_execution_ctx(); + /// let mut probe = array.validity()?.probe(ProbeUsage::Repeated); + /// assert!(probe.execute_is_valid(0, &mut ctx)?); + /// assert!(probe.execute_is_invalid(1, &mut ctx)?); + /// assert!(probe.execute_is_valid(2, &mut ctx)?); + /// # Ok::<(), vortex_error::VortexError>(()) + /// ``` + pub fn probe(&self, usage: ProbeUsage) -> ValidityProbe<'static> { + ValidityProbe::owned(self.clone(), usage) + } + /// Returns whether the `index` item is valid, using `ctx` to execute the validity array. #[inline] #[expect(deprecated)] From 8e498a1f7b87a3f95fc81211020567169ef6f64c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 21:50:35 +0000 Subject: [PATCH 8/9] refactor(array): deprecate the one-off validity accessors `Validity::probe` now offers the same per-row answers while retaining preparation across lookups, so the one-off accessors have a replacement they previously lacked. Deprecate `Validity::execute_is_valid` and `execute_is_null` in favour of `probe`, and repoint the already-deprecated `is_valid`/`is_null` notes at it too, since they named the accessors this commit deprecates. The replacement for a null check is `execute_is_invalid`, matching `ArrayRef::is_invalid` rather than the old `is_null` spelling. Signed-off-by: "Joe Isaacs" Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMRyEJHyh72KkM8o9paBeP --- vortex-array/src/validity.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/vortex-array/src/validity.rs b/vortex-array/src/validity.rs index 1ba84e6f3b1..06bcaf545c3 100644 --- a/vortex-array/src/validity.rs +++ b/vortex-array/src/validity.rs @@ -190,6 +190,11 @@ impl Validity { } /// Returns whether the `index` item is valid, using `ctx` to execute the validity array. + #[deprecated( + note = "Use `Validity::probe` instead, which retains preparation across lookups: \ + `validity.probe(ProbeUsage::Once).execute_is_valid(index, ctx)`, or `ProbeUsage::Repeated` \ + when reading more than one index from the same validity." + )] #[inline] #[expect(deprecated)] pub fn execute_is_valid(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { @@ -205,13 +210,22 @@ impl Validity { } /// Returns whether the `index` item is null, using `ctx` to execute the validity array. + #[deprecated( + note = "Use `Validity::probe` instead, which retains preparation across lookups: \ + `validity.probe(ProbeUsage::Once).execute_is_invalid(index, ctx)`, or \ + `ProbeUsage::Repeated` when reading more than one index from the same validity." + )] #[inline] + #[expect(deprecated)] pub fn execute_is_null(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult { Ok(!self.execute_is_valid(index, ctx)?) } /// Returns whether the `index` item is valid. - #[deprecated(note = "use `execute_is_valid` with an explicit `ExecutionCtx`")] + #[deprecated( + note = "Use `Validity::probe` with an explicit `ExecutionCtx` instead: \ + `validity.probe(ProbeUsage::Once).execute_is_valid(index, ctx)`." + )] #[inline] #[allow(clippy::disallowed_methods)] pub fn is_valid(&self, index: usize) -> VortexResult { @@ -219,7 +233,10 @@ impl Validity { } /// Returns whether the `index` item is null. - #[deprecated(note = "use `execute_is_null` with an explicit `ExecutionCtx`")] + #[deprecated( + note = "Use `Validity::probe` with an explicit `ExecutionCtx` instead: \ + `validity.probe(ProbeUsage::Once).execute_is_invalid(index, ctx)`." + )] #[inline] #[allow(clippy::disallowed_methods)] pub fn is_null(&self, index: usize) -> VortexResult { From 4fccdaceec736515ceafa8e2bafd632af289b53a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 21:55:11 +0000 Subject: [PATCH 9/9] refactor: expect the deprecated validity accessors at each site Annotate the callers of `Validity::execute_is_valid` and `execute_is_null` with `#[expect(deprecated)]`, so each suppression sits in the code that needs it and goes unfulfilled once that caller moves to `Validity::probe`. Sites were located by the compiler, as for the array accessors. One `#[rstest]` function takes a `#![expect(deprecated)]` at `mod tests` scope instead, since rstest discards lint attributes from its expansion. Signed-off-by: "Joe Isaacs" Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EMRyEJHyh72KkM8o9paBeP --- encodings/alp/src/alp/array.rs | 1 + encodings/fastlanes/src/rle/array/rle_compress.rs | 1 + vortex-array/benches/validity_is_valid.rs | 1 + vortex-array/src/arrays/listview/rebuild.rs | 2 ++ vortex-array/src/builders/fixed_size_list.rs | 6 ++++++ vortex-array/src/builders/listview.rs | 4 ++++ vortex-array/src/builders/primitive.rs | 1 + vortex-array/src/validity.rs | 2 ++ vortex-duckdb/src/duckdb/vector.rs | 2 ++ 9 files changed, 20 insertions(+) diff --git a/encodings/alp/src/alp/array.rs b/encodings/alp/src/alp/array.rs index 3e2010a1ad5..1e9570a5969 100644 --- a/encodings/alp/src/alp/array.rs +++ b/encodings/alp/src/alp/array.rs @@ -481,6 +481,7 @@ impl ValidityChild for ALP { #[cfg(test)] mod tests { + #![expect(deprecated)] use std::f64::consts::PI; use std::sync::LazyLock; diff --git a/encodings/fastlanes/src/rle/array/rle_compress.rs b/encodings/fastlanes/src/rle/array/rle_compress.rs index f623b84d778..c3822c9dfcd 100644 --- a/encodings/fastlanes/src/rle/array/rle_compress.rs +++ b/encodings/fastlanes/src/rle/array/rle_compress.rs @@ -442,6 +442,7 @@ mod tests { /// This simulates a compressor that doesn't preserve index values at null /// positions, which can happen when indices are further compressed and the /// compressor clobbers invalid entries with arbitrary data. + #[expect(deprecated)] fn with_random_invalid_indices( rle: &RLEArray, ctx: &mut ExecutionCtx, diff --git a/vortex-array/benches/validity_is_valid.rs b/vortex-array/benches/validity_is_valid.rs index dc22fcb5478..0788254455e 100644 --- a/vortex-array/benches/validity_is_valid.rs +++ b/vortex-array/benches/validity_is_valid.rs @@ -41,6 +41,7 @@ static SESSION: LazyLock = LazyLock::new(array_session); /// Per-element validity check over array-backed validity (the antipattern). This mirrors the /// deprecated `Validity::is_valid(i)`: a fresh `ExecutionCtx` plus a scalar lookup on every call. #[divan::bench(args = SIZES)] +#[expect(deprecated)] fn is_valid_per_element(bencher: Bencher, len: usize) { let validity = array_validity(len); bencher diff --git a/vortex-array/src/arrays/listview/rebuild.rs b/vortex-array/src/arrays/listview/rebuild.rs index 727f458235c..fbaf7014630 100644 --- a/vortex-array/src/arrays/listview/rebuild.rs +++ b/vortex-array/src/arrays/listview/rebuild.rs @@ -505,6 +505,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_rebuild_flatten_with_nullable() -> VortexResult<()> { use crate::arrays::BoolArray; @@ -547,6 +548,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_rebuild_flatten_null_row_uses_valid_empty_range() -> VortexResult<()> { let elements = PrimitiveArray::from_iter(vec![1i32, 2, 3, 4]).into_array(); let offsets = PrimitiveArray::from_iter(vec![0u32, 1, 2]).into_array(); diff --git a/vortex-array/src/builders/fixed_size_list.rs b/vortex-array/src/builders/fixed_size_list.rs index dd9808706f4..471f42fb886 100644 --- a/vortex-array/src/builders/fixed_size_list.rs +++ b/vortex-array/src/builders/fixed_size_list.rs @@ -553,6 +553,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_nullable_lists_non_nullable_elements() { let mut ctx = array_session().create_execution_ctx(); let dtype: Arc = Arc::new(DType::Primitive(I32, NonNullable)); @@ -691,6 +692,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_append_nulls() { let mut ctx = array_session().create_execution_ctx(); // Elements must be nullable if we're going to append null lists @@ -726,6 +728,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_append_scalar_nulls() { let mut ctx = array_session().create_execution_ctx(); // Elements must be nullable if we're going to append null lists @@ -815,6 +818,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_extend_from_array() { let mut ctx = array_session().create_execution_ctx(); let dtype: Arc = Arc::new(I32.into()); @@ -895,6 +899,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_extend_degenerate_arrays() { let mut ctx = array_session().create_execution_ctx(); let dtype: Arc = Arc::new(I32.into()); @@ -1020,6 +1025,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_mixed_operations() { let mut ctx = array_session().create_execution_ctx(); // Use nullable elements since we'll be appending nulls diff --git a/vortex-array/src/builders/listview.rs b/vortex-array/src/builders/listview.rs index a0c326b7f37..b03d41beece 100644 --- a/vortex-array/src/builders/listview.rs +++ b/vortex-array/src/builders/listview.rs @@ -709,6 +709,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_basic_append_and_nulls() { let mut ctx = array_session().create_execution_ctx(); let dtype: Arc = Arc::new(I32.into()); @@ -861,6 +862,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_builder_trait_methods() { let mut ctx = array_session().create_execution_ctx(); let dtype: Arc = Arc::new(I32.into()); @@ -919,6 +921,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_extend_from_array() { let mut ctx = array_session().create_execution_ctx(); let dtype: Arc = Arc::new(I32.into()); @@ -1120,6 +1123,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_extend_from_array_overlapping_listview() { let mut ctx = array_session().create_execution_ctx(); let dtype: Arc = Arc::new(I32.into()); diff --git a/vortex-array/src/builders/primitive.rs b/vortex-array/src/builders/primitive.rs index 8940611387a..c24d7d64bde 100644 --- a/vortex-array/src/builders/primitive.rs +++ b/vortex-array/src/builders/primitive.rs @@ -696,6 +696,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_append_scalar() { use crate::dtype::DType; use crate::scalar::Scalar; diff --git a/vortex-array/src/validity.rs b/vortex-array/src/validity.rs index 06bcaf545c3..d3c984cb82d 100644 --- a/vortex-array/src/validity.rs +++ b/vortex-array/src/validity.rs @@ -228,6 +228,7 @@ impl Validity { )] #[inline] #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn is_valid(&self, index: usize) -> VortexResult { self.execute_is_valid(index, &mut legacy_session().create_execution_ctx()) } @@ -239,6 +240,7 @@ impl Validity { )] #[inline] #[allow(clippy::disallowed_methods)] + #[expect(deprecated)] pub fn is_null(&self, index: usize) -> VortexResult { self.execute_is_null(index, &mut legacy_session().create_execution_ctx()) } diff --git a/vortex-duckdb/src/duckdb/vector.rs b/vortex-duckdb/src/duckdb/vector.rs index 47cad049516..cf167e8ed90 100644 --- a/vortex-duckdb/src/duckdb/vector.rs +++ b/vortex-duckdb/src/duckdb/vector.rs @@ -422,6 +422,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_create_validity_single_element() { let len = 1; let logical_type = LogicalType::new(DUCKDB_TYPE::DUCKDB_TYPE_INTEGER); @@ -440,6 +441,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn test_create_validity_single_element_valid() { let len = 1; let logical_type = LogicalType::new(DUCKDB_TYPE::DUCKDB_TYPE_INTEGER);