vortex_array/arrays/list/
array.rs1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::sync::Arc;
7
8use num_traits::AsPrimitive;
9use vortex_error::VortexExpect;
10use vortex_error::VortexResult;
11use vortex_error::vortex_bail;
12use vortex_error::vortex_ensure;
13use vortex_error::vortex_ensure_eq;
14use vortex_error::vortex_panic;
15
16use crate::ArrayRef;
17use crate::ArraySlots;
18use crate::Canonical;
19use crate::ExecutionCtx;
20use crate::IntoArray;
21use crate::aggregate_fn::NumericalAggregateOpts;
22use crate::aggregate_fn::fns::min_max::min_max;
23use crate::array::Array;
24use crate::array::ArrayParts;
25use crate::array::TypedArrayRef;
26use crate::array::child_to_validity;
27use crate::array::validity_to_child;
28use crate::array_slots;
29use crate::arrays::ConstantArray;
30use crate::arrays::List;
31use crate::arrays::ListArray;
32use crate::arrays::Primitive;
33use crate::builtins::ArrayBuiltins;
34use crate::dtype::DType;
35use crate::dtype::NativePType;
36use crate::match_each_integer_ptype;
37use crate::match_each_native_ptype;
38use crate::scalar_fn::fns::operators::Operator;
39use crate::validity::Validity;
40
41#[array_slots(List)]
42pub struct ListSlots {
43 #[slot(0)]
45 pub elements: ArrayRef,
46 #[slot(1)]
48 pub offsets: ArrayRef,
49 #[slot(2)]
51 pub validity: Option<ArrayRef>,
52}
53
54#[derive(Clone, Debug, Default)]
110pub struct ListData;
111
112impl Display for ListData {
113 fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
114 Ok(())
115 }
116}
117
118pub struct ListDataParts {
119 pub elements: ArrayRef,
120 pub offsets: ArrayRef,
121 pub validity: Validity,
122 pub dtype: DType,
123}
124
125impl ListData {
126 pub(crate) fn make_slots(
127 elements: &ArrayRef,
128 offsets: &ArrayRef,
129 validity: &Validity,
130 len: usize,
131 ) -> ArraySlots {
132 ListSlots {
133 elements: elements.clone(),
134 offsets: offsets.clone(),
135 validity: validity_to_child(validity, len),
136 }
137 .into_slots()
138 }
139
140 pub fn build(
147 elements: ArrayRef,
148 offsets: ArrayRef,
149 validity: Validity,
150 ctx: &mut ExecutionCtx,
151 ) -> Self {
152 Self::try_build(elements, offsets, validity, ctx).vortex_expect("ListArray new")
153 }
154
155 pub(crate) fn try_build(
164 elements: ArrayRef,
165 offsets: ArrayRef,
166 validity: Validity,
167 ctx: &mut ExecutionCtx,
168 ) -> VortexResult<Self> {
169 Self::validate(&elements, &offsets, &validity, ctx)?;
170
171 Ok(unsafe { Self::new_unchecked() })
173 }
174
175 pub unsafe fn new_unchecked() -> Self {
192 Self
193 }
194
195 pub fn validate(
199 elements: &ArrayRef,
200 offsets: &ArrayRef,
201 validity: &Validity,
202 ctx: &mut ExecutionCtx,
203 ) -> VortexResult<()> {
204 vortex_ensure!(
206 !offsets.is_empty(),
207 InvalidArgument: "Offsets must have at least one element, [0] for an empty list"
208 );
209
210 vortex_ensure!(
212 offsets.dtype().is_int() && !offsets.dtype().is_nullable(),
213 InvalidArgument: "offsets have invalid type {}",
214 offsets.dtype()
215 );
216
217 let offsets_ptype = offsets.dtype().as_ptype();
219
220 if let Some(is_sorted) = offsets.statistics().compute_is_sorted(ctx) {
222 vortex_ensure!(is_sorted, InvalidArgument: "offsets must be sorted");
223 } else {
224 vortex_bail!(InvalidArgument: "offsets must report is_sorted statistic");
225 }
226
227 if let Some(min_max) = min_max(offsets, ctx, NumericalAggregateOpts::default())? {
230 match_each_integer_ptype!(offsets_ptype, |P| {
231 #[allow(clippy::absurd_extreme_comparisons, unused_comparisons)]
232 {
233 let max = min_max
234 .max
235 .as_primitive()
236 .as_::<P>()
237 .vortex_expect("offsets type must fit offsets values");
238 let min = min_max
239 .min
240 .as_primitive()
241 .as_::<P>()
242 .vortex_expect("offsets type must fit offsets values");
243
244 vortex_ensure!(
245 min >= 0,
246 InvalidArgument: "offsets minimum {min} outside valid range [0, {max}]"
247 );
248
249 vortex_ensure!(
250 max <= P::try_from(elements.len()).unwrap_or_else(|_| vortex_panic!(
251 "Offsets type {} must be able to fit elements length {}",
252 <P as NativePType>::PTYPE,
253 elements.len()
254 )),
255 InvalidArgument: "Max offset {max} is beyond the length of the elements array {}",
256 elements.len()
257 );
258 }
259 })
260 } else {
261 vortex_bail!(
263 InvalidArgument: "offsets array with encoding {} must support min_max compute function",
264 offsets.encoding_id()
265 );
266 };
267
268 if let Some(validity_len) = validity.maybe_len() {
270 vortex_ensure_eq!(
271 validity_len,
272 offsets.len() - 1,
273 InvalidArgument: "validity length does not match array size",
274 );
275 }
276
277 Ok(())
278 }
279 }
284
285pub trait ListArrayExt: ListArraySlotsExt {
286 fn nullability(&self) -> crate::dtype::Nullability {
287 match self.dtype() {
288 DType::List(_, nullability) => *nullability,
289 _ => unreachable!("ListArrayExt requires a list dtype"),
290 }
291 }
292
293 fn list_validity(&self) -> Validity {
294 child_to_validity(
295 self.slots()[ListSlots::VALIDITY].as_ref(),
296 self.nullability(),
297 )
298 }
299
300 fn offset_at(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
301 vortex_ensure!(
302 index <= self.len(),
303 "Index {index} out of bounds 0..={}",
304 self.len()
305 );
306
307 if let Some(p) = self.offsets().as_opt::<Primitive>() {
308 Ok(match_each_native_ptype!(p.ptype(), |P| {
309 p.as_slice::<P>()[index].as_()
310 }))
311 } else {
312 self.offsets()
313 .execute_scalar(index, ctx)?
314 .as_primitive()
315 .as_::<usize>()
316 .ok_or_else(|| vortex_error::vortex_err!("offset value does not fit in usize"))
317 }
318 }
319
320 fn list_elements_at(&self, index: usize, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> {
321 let start = self.offset_at(index, ctx)?;
322 let end = self.offset_at(index + 1, ctx)?;
323 self.elements().slice(start..end)
324 }
325
326 fn sliced_elements(&self, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> {
327 let start = self.offset_at(0, ctx)?;
328 let end = self.offset_at(self.len(), ctx)?;
329 self.elements().slice(start..end)
330 }
331
332 fn element_dtype(&self) -> &DType {
333 self.elements().dtype()
334 }
335
336 fn reset_offsets(&self, recurse: bool, ctx: &mut ExecutionCtx) -> VortexResult<Array<List>> {
337 let mut elements = self.sliced_elements(ctx)?;
338 if recurse && elements.is_canonical() {
339 let compacted = elements
340 .execute::<Canonical>(ctx)?
341 .compact(ctx)?
342 .into_array();
343 elements = compacted;
344 } else if recurse && let Some(child_list_array) = elements.as_opt::<List>() {
345 elements = child_list_array
346 .into_owned()
347 .reset_offsets(recurse, ctx)?
348 .into_array();
349 }
350
351 let offsets = self.offsets();
352 let first_offset = offsets.execute_scalar(0, ctx)?;
353 let adjusted_offsets = offsets.clone().binary(
354 ConstantArray::new(first_offset, offsets.len()).into_array(),
355 Operator::Sub,
356 )?;
357
358 Ok(unsafe { ListArray::new_unchecked(elements, adjusted_offsets, self.list_validity()) })
360 }
361}
362impl<T: TypedArrayRef<List>> ListArrayExt for T {}
363
364impl Array<List> {
365 pub fn new(
367 elements: ArrayRef,
368 offsets: ArrayRef,
369 validity: Validity,
370 ctx: &mut ExecutionCtx,
371 ) -> Self {
372 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
373 let len = offsets.len().saturating_sub(1);
374 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
375 let data = ListData::build(elements, offsets, validity, ctx);
376 unsafe { Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data, slots)) }
377 }
378
379 pub fn try_new(
381 elements: ArrayRef,
382 offsets: ArrayRef,
383 validity: Validity,
384 ctx: &mut ExecutionCtx,
385 ) -> VortexResult<Self> {
386 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
387 let len = offsets.len().saturating_sub(1);
388 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
389 let data = ListData::try_build(elements, offsets, validity, ctx)?;
390 Ok(unsafe { Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data, slots)) })
391 }
392
393 pub unsafe fn new_unchecked(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
399 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
400 let len = offsets.len().saturating_sub(1);
401 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
402 let data = unsafe { ListData::new_unchecked() };
403 unsafe { Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data, slots)) }
404 }
405
406 pub fn into_data_parts(self) -> ListDataParts {
407 let dtype = self.dtype().clone();
408 let elements = self.slots()[ListSlots::ELEMENTS]
409 .clone()
410 .vortex_expect("ListArray elements slot");
411 let offsets = self.slots()[ListSlots::OFFSETS]
412 .clone()
413 .vortex_expect("ListArray offsets slot");
414 let validity = self.list_validity();
415 ListDataParts {
416 elements,
417 offsets,
418 validity,
419 dtype,
420 }
421 }
422}