use std::mem::needs_drop;
use vortex_error::VortexError;
use vortex_error::VortexResult;
use vortex_error::vortex_err;
use crate::ArrayRef;
use crate::ExecutionCtx;
use crate::array::ArrayView;
use crate::array::VTable;
use crate::array::probe::RepeatedArrayProbe;
use crate::array::probe::RepeatedState;
use crate::array::probe::repeated::child_probe;
use crate::array::probe::repeated::is_valid_scalar;
use crate::arrays::Primitive;
use crate::scalar::Scalar;
use crate::validity::Validity;
use crate::vtable::OperationsVTable;
pub enum ArrayProbe<'a> {
Once(&'a ArrayRef),
Repeated(&'a mut RepeatedArrayProbe),
}
const _: () = assert!(!needs_drop::<ArrayProbe<'_>>());
const _: () = assert!(!needs_drop::<ProbeState<'_, Primitive>>());
impl ArrayProbe<'_> {
#[inline]
pub fn array(&self) -> &ArrayRef {
match self {
Self::Once(array) => array,
Self::Repeated(probe) => probe.array(),
}
}
#[inline]
pub fn execute_scalar(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<Scalar> {
match self {
Self::Once(array) => execute_scalar_once(array, index, ctx),
Self::Repeated(probe) => probe.execute_scalar(index, ctx),
}
}
#[inline]
pub fn execute_is_valid(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
match self {
Self::Once(array) => execute_is_valid_once(array, index, ctx),
Self::Repeated(probe) => probe.execute_is_valid(index, ctx),
}
}
#[inline]
pub fn execute_is_invalid(
&mut self,
index: usize,
ctx: &mut ExecutionCtx,
) -> VortexResult<bool> {
Ok(!self.execute_is_valid(index, ctx)?)
}
}
#[inline]
fn execute_scalar_once(
array: &ArrayRef,
index: usize,
ctx: &mut ExecutionCtx,
) -> VortexResult<Scalar> {
check_bounds(array, index)?;
check_dtype(
array,
array.dyn_array().probe_scalar_once(array, index, ctx),
)
}
#[inline]
fn execute_is_valid_once(
array: &ArrayRef,
index: usize,
ctx: &mut ExecutionCtx,
) -> VortexResult<bool> {
check_bounds(array, index)?;
is_valid_once(array, index, ctx)
}
#[inline]
fn is_valid_once(array: &ArrayRef, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
if !array.dtype().is_nullable() {
return Ok(true);
}
match array.validity()? {
Validity::NonNullable | Validity::AllValid => Ok(true),
Validity::AllInvalid => Ok(false),
Validity::Array(validity) => is_valid_scalar(validity.execute_scalar(index, ctx)?, index),
}
}
#[inline]
pub(super) fn check_dtype(array: &ArrayRef, result: VortexResult<Scalar>) -> VortexResult<Scalar> {
result.inspect(|scalar| {
debug_assert_eq!(scalar.dtype(), array.dtype(), "Scalar dtype mismatch");
})
}
#[inline]
pub(super) fn check_bounds(array: &ArrayRef, index: usize) -> VortexResult<()> {
if index >= array.len() {
return Err(out_of_bounds(index, array.len()));
}
Ok(())
}
#[cold]
#[inline(never)]
fn out_of_bounds(index: usize, len: usize) -> VortexError {
vortex_err!(OutOfBounds: index, 0, len)
}
pub type EncodingProbeState<V> =
<<V as VTable>::OperationsVTable as OperationsVTable<V>>::ProbeState;
pub struct ProbeState<'a, V: VTable> {
array: ArrayView<'a, V>,
retained: Option<&'a mut RepeatedState<EncodingProbeState<V>>>,
}
impl<'a, V: VTable> ProbeState<'a, V> {
#[inline]
pub fn once(array: ArrayView<'a, V>) -> Self {
Self {
array,
retained: None,
}
}
#[inline]
pub(crate) fn repeated(
array: ArrayView<'a, V>,
retained: &'a mut RepeatedState<EncodingProbeState<V>>,
) -> Self {
Self {
array,
retained: Some(retained),
}
}
#[inline]
pub fn array(&self) -> ArrayView<'a, V> {
self.array
}
#[inline]
pub fn is_valid(&mut self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<bool> {
let array = self.array.array();
match &mut self.retained {
None => is_valid_once(array, index, ctx),
Some(retained) => retained.is_valid(array, index, ctx),
}
}
#[inline]
pub fn retained(&mut self) -> Option<&mut EncodingProbeState<V>> {
self.retained.as_deref_mut().map(RepeatedState::state_mut)
}
#[inline]
pub fn split(&mut self) -> (Option<&mut EncodingProbeState<V>>, ProbeChildren<'_, 'a, V>) {
let array = self.array;
match &mut self.retained {
None => (None, ProbeChildren { array, slots: None }),
Some(repeated) => {
let (state, slots) = repeated.split_mut();
let children = ProbeChildren {
array,
slots: Some(slots),
};
(Some(state), children)
}
}
}
#[inline]
pub fn slot(&mut self, slot: usize) -> VortexResult<Option<ArrayProbe<'_>>> {
let parent = self.array.array();
match &mut self.retained {
None => Ok(child_of(parent, slot)?.map(ArrayProbe::Once)),
Some(repeated) => {
Ok(child_probe(repeated.split_mut().1, parent, slot)?.map(ArrayProbe::Repeated))
}
}
}
}
pub struct ProbeChildren<'s, 'a, V: VTable> {
array: ArrayView<'a, V>,
slots: Option<&'s mut Vec<Option<RepeatedArrayProbe>>>,
}
impl<V: VTable> ProbeChildren<'_, '_, V> {
#[inline]
pub fn slot(&mut self, slot: usize) -> VortexResult<Option<ArrayProbe<'_>>> {
let parent = self.array.array();
match &mut self.slots {
None => Ok(child_of(parent, slot)?.map(ArrayProbe::Once)),
Some(slots) => Ok(child_probe(slots, parent, slot)?.map(ArrayProbe::Repeated)),
}
}
}
#[inline]
pub(super) fn child_of(parent: &ArrayRef, slot: usize) -> VortexResult<Option<&ArrayRef>> {
Ok(parent
.slots()
.get(slot)
.ok_or_else(|| vortex_err!("Probe slot {slot} is out of bounds"))?
.as_ref())
}
#[cfg(test)]
mod tests {
use vortex_error::VortexResult;
use vortex_error::vortex_err;
use super::*;
use crate::VortexSessionExecute;
use crate::array::IntoArray;
use crate::arrays::BoolArray;
use crate::arrays::DictArray;
use crate::arrays::PrimitiveArray;
use crate::arrays::Struct;
use crate::arrays::StructArray;
fn nullable_ints() -> ArrayRef {
PrimitiveArray::from_option_iter([Some(10i32), None, Some(30)]).into_array()
}
fn check_reads(mut probe: ArrayProbe<'_>, ctx: &mut ExecutionCtx) -> VortexResult<()> {
assert!(probe.execute_scalar(3, ctx).is_err());
assert!(probe.execute_is_valid(3, ctx).is_err());
assert!(probe.execute_scalar(1, ctx)?.is_null());
assert!(!probe.execute_is_valid(1, ctx)?);
assert!(probe.execute_is_invalid(1, ctx)?);
assert_eq!(probe.execute_scalar(2, ctx)?, Scalar::from(Some(30i32)));
assert_eq!(probe.execute_scalar(0, ctx)?, Scalar::from(Some(10i32)));
Ok(())
}
#[test]
fn once_probe_checks_bounds_and_nulls() -> VortexResult<()> {
let mut ctx = crate::array_session().create_execution_ctx();
let array = nullable_ints();
check_reads(array.probe(), &mut ctx)
}
#[test]
fn default_probe_scalar_resolves_nulls() -> VortexResult<()> {
let mut ctx = crate::array_session().create_execution_ctx();
let array = BoolArray::from_iter([Some(true), None, Some(false)]).into_array();
check_bool_reads(array.probe(), &mut ctx)?;
check_bool_reads(array.repeated_probe().as_probe(), &mut ctx)
}
fn check_bool_reads(mut probe: ArrayProbe<'_>, ctx: &mut ExecutionCtx) -> VortexResult<()> {
assert_eq!(probe.execute_scalar(0, ctx)?, Scalar::from(Some(true)));
assert!(probe.execute_scalar(1, ctx)?.is_null());
assert_eq!(probe.execute_scalar(2, ctx)?, Scalar::from(Some(false)));
Ok(())
}
#[test]
fn wrapper_passes_child_nulls_through() -> VortexResult<()> {
let mut ctx = crate::array_session().create_execution_ctx();
let codes = PrimitiveArray::from_iter([0u32, 1, 0, 2]).into_array();
let values = PrimitiveArray::from_option_iter([Some(7i64), None, Some(9)]).into_array();
let array = DictArray::try_new(codes, values)?.into_array();
let expected = [Some(7i64), None, Some(7), Some(9)];
let mut once = array.probe();
let mut repeated = array.repeated_probe();
for (index, value) in expected.into_iter().enumerate() {
assert_eq!(once.execute_scalar(index, &mut ctx)?, Scalar::from(value));
assert_eq!(
repeated.execute_scalar(index, &mut ctx)?,
Scalar::from(value)
);
}
Ok(())
}
#[test]
fn dict_with_nullable_codes_and_values() -> VortexResult<()> {
let mut ctx = crate::array_session().create_execution_ctx();
let codes = PrimitiveArray::from_option_iter([Some(0u32), None, Some(1), Some(2), None])
.into_array();
let values = PrimitiveArray::from_option_iter([Some(7i64), None, Some(9)]).into_array();
let array = DictArray::try_new(codes, values)?.into_array();
let expected = [Some(7i64), None, None, Some(9), None];
for (array, expected) in [
(array.clone(), &expected[..]),
(array.slice(1..4)?, &expected[1..4]),
] {
let mut once = array.probe();
let mut repeated = array.repeated_probe();
for (index, value) in expected.iter().enumerate().rev() {
assert_eq!(once.execute_is_valid(index, &mut ctx)?, value.is_some());
assert_eq!(repeated.execute_is_valid(index, &mut ctx)?, value.is_some());
assert_eq!(once.execute_scalar(index, &mut ctx)?, Scalar::from(*value));
assert_eq!(
repeated.execute_scalar(index, &mut ctx)?,
Scalar::from(*value)
);
}
}
Ok(())
}
#[test]
fn state_validity_follows_read_policy() -> VortexResult<()> {
let mut ctx = crate::array_session().create_execution_ctx();
let array = nullable_ints();
let typed = array
.as_opt::<Primitive>()
.ok_or_else(|| vortex_err!("expected a primitive"))?;
let mut once = ProbeState::once(typed);
assert!(once.is_valid(0, &mut ctx)?);
assert!(!once.is_valid(1, &mut ctx)?);
let mut repeated = RepeatedState::<()>::default();
let mut state = ProbeState::repeated(typed, &mut repeated);
assert!(!state.is_valid(1, &mut ctx)?);
assert!(state.is_valid(2, &mut ctx)?);
let all_valid = PrimitiveArray::from_option_iter([Some(1i32)]).into_array();
let typed = all_valid
.as_opt::<Primitive>()
.ok_or_else(|| vortex_err!("expected a primitive"))?;
assert!(ProbeState::once(typed).is_valid(0, &mut ctx)?);
Ok(())
}
#[test]
fn once_state_reads_children_without_retaining() -> VortexResult<()> {
let mut ctx = crate::array_session().create_execution_ctx();
let array = struct_of_two_fields()?;
let typed = array
.as_opt::<Struct>()
.ok_or_else(|| vortex_err!("expected a struct"))?;
let mut state = ProbeState::once(typed);
assert!(state.slot(5).is_err());
assert!(state.slot(0)?.is_none());
{
let mut field = state.slot(2)?.ok_or_else(|| vortex_err!("missing field"))?;
assert!(matches!(field, ArrayProbe::Once(_)));
assert_eq!(field.array().len(), 2);
assert_eq!(field.execute_scalar(1, &mut ctx)?, Scalar::from(4i64));
assert!(field.execute_is_valid(0, &mut ctx)?);
}
assert!(state.retained().is_none());
let (retained, mut children) = state.split();
assert!(retained.is_none());
let mut field = children
.slot(1)?
.ok_or_else(|| vortex_err!("missing field"))?;
assert_eq!(field.execute_scalar(1, &mut ctx)?, Scalar::from(2i32));
Ok(())
}
fn struct_of_two_fields() -> VortexResult<ArrayRef> {
Ok(StructArray::from_fields(&[
("a", PrimitiveArray::from_iter([1i32, 2]).into_array()),
("b", PrimitiveArray::from_iter([3i64, 4]).into_array()),
])?
.into_array())
}
}