use std::sync::Arc;
use num_traits::AsPrimitive;
use vortex_dtype::{DType, IntegerPType, match_each_integer_ptype};
use vortex_error::{VortexExpect, VortexResult, vortex_ensure, vortex_err};
use crate::arrays::PrimitiveVTable;
use crate::stats::ArrayStats;
use crate::validity::Validity;
use crate::{Array, ArrayRef, ToCanonical};
#[derive(Clone, Debug)]
pub struct ListViewArray {
pub(super) dtype: DType,
elements: ArrayRef,
offsets: ArrayRef,
sizes: ArrayRef,
pub(super) validity: Validity,
pub(super) stats_set: ArrayStats,
}
impl ListViewArray {
pub fn new(elements: ArrayRef, offsets: ArrayRef, sizes: ArrayRef, validity: Validity) -> Self {
Self::try_new(elements, offsets, sizes, validity)
.vortex_expect("ListViewArray construction failed")
}
pub fn try_new(
elements: ArrayRef,
offsets: ArrayRef,
sizes: ArrayRef,
validity: Validity,
) -> VortexResult<Self> {
Self::validate(&elements, &offsets, &sizes, &validity)?;
Ok(unsafe { Self::new_unchecked(elements, offsets, sizes, validity) })
}
pub unsafe fn new_unchecked(
elements: ArrayRef,
offsets: ArrayRef,
sizes: ArrayRef,
validity: Validity,
) -> Self {
#[cfg(debug_assertions)]
Self::validate(&elements, &offsets, &sizes, &validity)
.vortex_expect("[Debug Assertion]: Invalid `ListViewArray` parameters");
Self {
dtype: DType::List(Arc::new(elements.dtype().clone()), validity.nullability()),
elements,
offsets,
sizes,
validity,
stats_set: Default::default(),
}
}
pub fn validate(
elements: &dyn Array,
offsets: &dyn Array,
sizes: &dyn Array,
validity: &Validity,
) -> VortexResult<()> {
vortex_ensure!(
offsets.dtype().is_int() && !offsets.dtype().is_nullable(),
"offsets must be non-nullable integer array, got {}",
offsets.dtype()
);
vortex_ensure!(
sizes.dtype().is_int() && !sizes.dtype().is_nullable(),
"sizes must be non-nullable integer array, got {}",
sizes.dtype()
);
vortex_ensure!(
offsets.len() == sizes.len(),
"offsets and sizes must have the same length, got {} and {}",
offsets.len(),
sizes.len()
);
let size_ptype = sizes.dtype().as_ptype();
let offset_ptype = offsets.dtype().as_ptype();
let size_max = sizes.dtype().as_ptype().max_value_as_u64();
let offset_max = offsets.dtype().as_ptype().max_value_as_u64();
vortex_ensure!(
size_max <= offset_max,
"size type {:?} (max {}) must fit within offset type {:?} (max {})",
size_ptype,
size_max,
offset_ptype,
offset_max
);
let offsets_primitive = offsets.to_primitive();
let sizes_primitive = sizes.to_primitive();
match_each_integer_ptype!(offset_ptype, |O| {
match_each_integer_ptype!(size_ptype, |S| {
let offsets_slice = offsets_primitive.as_slice::<O>();
let sizes_slice = sizes_primitive.as_slice::<S>();
validate_offsets_and_sizes::<O, S>(
offsets_slice,
sizes_slice,
elements.len() as u64,
)?;
})
});
if let Some(validity_len) = validity.maybe_len() {
vortex_ensure!(
validity_len == offsets.len(),
"validity with size {validity_len} does not match array size {}",
offsets.len()
);
}
Ok(())
}
pub fn offset_at(&self, index: usize) -> usize {
assert!(
index < self.len(),
"Index {index} out of bounds 0..{}",
self.len()
);
self.offsets
.as_opt::<PrimitiveVTable>()
.map(|p| match_each_integer_ptype!(p.ptype(), |P| { p.as_slice::<P>()[index].as_() }))
.unwrap_or_else(|| {
self.offsets
.scalar_at(index)
.as_primitive()
.as_::<usize>()
.vortex_expect("offset must fit in usize")
})
}
pub fn size_at(&self, index: usize) -> usize {
assert!(
index < self.len(),
"Index {} out of bounds 0..{}",
index,
self.len()
);
self.sizes
.as_opt::<PrimitiveVTable>()
.map(|p| match_each_integer_ptype!(p.ptype(), |P| { p.as_slice::<P>()[index].as_() }))
.unwrap_or_else(|| {
self.sizes
.scalar_at(index)
.as_primitive()
.as_::<usize>()
.vortex_expect("size must fit in usize")
})
}
pub fn list_elements_at(&self, index: usize) -> ArrayRef {
let offset = self.offset_at(index);
let size = self.size_at(index);
self.elements().slice(offset..offset + size)
}
pub fn offsets(&self) -> &ArrayRef {
&self.offsets
}
pub fn sizes(&self) -> &ArrayRef {
&self.sizes
}
pub fn elements(&self) -> &ArrayRef {
&self.elements
}
}
fn validate_offsets_and_sizes<O, S>(
offsets_slice: &[O],
sizes_slice: &[S],
elements_len: u64,
) -> VortexResult<()>
where
O: IntegerPType,
S: IntegerPType,
{
debug_assert_eq!(offsets_slice.len(), sizes_slice.len());
#[allow(clippy::absurd_extreme_comparisons, unused_comparisons)]
for i in 0..offsets_slice.len() {
let offset = offsets_slice[i];
let size = sizes_slice[i];
vortex_ensure!(offset >= O::zero(), "cannot have negative offsets");
vortex_ensure!(size >= S::zero(), "cannot have negative size");
let offset_u64 = offset
.to_u64()
.ok_or_else(|| vortex_err!("offset[{i}] = {offset:?} cannot be converted to u64"))?;
let size_u64 = size
.to_u64()
.ok_or_else(|| vortex_err!("size[{i}] = {size:?} cannot be converted to u64"))?;
let end = offset_u64.checked_add(size_u64).ok_or_else(|| {
vortex_err!("offset[{i}] ({offset_u64}) + size[{i}] ({size_u64}) would overflow u64")
})?;
vortex_ensure!(
end <= elements_len,
"offset[{i}] + size[{i}] = {end} exceeds elements length {elements_len}",
);
}
Ok(())
}