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::VortexSessionExecute;
22use crate::aggregate_fn::NumericalAggregateOpts;
23use crate::aggregate_fn::fns::min_max::min_max;
24use crate::array::Array;
25use crate::array::ArrayParts;
26use crate::array::TypedArrayRef;
27use crate::array::child_to_validity;
28use crate::array::validity_to_child;
29use crate::array_slots;
30use crate::arrays::ConstantArray;
31use crate::arrays::List;
32use crate::arrays::ListArray;
33use crate::arrays::Primitive;
34use crate::builtins::ArrayBuiltins;
35use crate::dtype::DType;
36use crate::dtype::NativePType;
37use crate::legacy_session;
38use crate::match_each_integer_ptype;
39use crate::match_each_native_ptype;
40use crate::scalar_fn::fns::operators::Operator;
41use crate::validity::Validity;
42
43#[array_slots(List)]
44pub struct ListSlots {
45 #[slot(0)]
47 pub elements: ArrayRef,
48 #[slot(1)]
50 pub offsets: ArrayRef,
51 #[slot(2)]
53 pub validity: Option<ArrayRef>,
54}
55
56#[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(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
147 Self::try_build(elements, offsets, validity).vortex_expect("ListArray new")
148 }
149
150 pub(crate) fn try_build(
159 elements: ArrayRef,
160 offsets: ArrayRef,
161 validity: Validity,
162 ) -> VortexResult<Self> {
163 Self::validate(&elements, &offsets, &validity)?;
164
165 Ok(unsafe { Self::new_unchecked() })
167 }
168
169 pub unsafe fn new_unchecked() -> Self {
186 Self
187 }
188
189 #[allow(clippy::disallowed_methods)]
193 pub fn validate(
194 elements: &ArrayRef,
195 offsets: &ArrayRef,
196 validity: &Validity,
197 ) -> VortexResult<()> {
198 vortex_ensure!(
200 !offsets.is_empty(),
201 InvalidArgument: "Offsets must have at least one element, [0] for an empty list"
202 );
203
204 vortex_ensure!(
206 offsets.dtype().is_int() && !offsets.dtype().is_nullable(),
207 InvalidArgument: "offsets have invalid type {}",
208 offsets.dtype()
209 );
210
211 let offsets_ptype = offsets.dtype().as_ptype();
213 let mut ctx = legacy_session().create_execution_ctx();
214
215 if let Some(is_sorted) = offsets.statistics().compute_is_sorted(&mut ctx) {
217 vortex_ensure!(is_sorted, InvalidArgument: "offsets must be sorted");
218 } else {
219 vortex_bail!(InvalidArgument: "offsets must report is_sorted statistic");
220 }
221
222 if let Some(min_max) = min_max(offsets, &mut ctx, NumericalAggregateOpts::default())? {
225 match_each_integer_ptype!(offsets_ptype, |P| {
226 #[allow(clippy::absurd_extreme_comparisons, unused_comparisons)]
227 {
228 let max = min_max
229 .max
230 .as_primitive()
231 .as_::<P>()
232 .vortex_expect("offsets type must fit offsets values");
233 let min = min_max
234 .min
235 .as_primitive()
236 .as_::<P>()
237 .vortex_expect("offsets type must fit offsets values");
238
239 vortex_ensure!(
240 min >= 0,
241 InvalidArgument: "offsets minimum {min} outside valid range [0, {max}]"
242 );
243
244 vortex_ensure!(
245 max <= P::try_from(elements.len()).unwrap_or_else(|_| vortex_panic!(
246 "Offsets type {} must be able to fit elements length {}",
247 <P as NativePType>::PTYPE,
248 elements.len()
249 )),
250 InvalidArgument: "Max offset {max} is beyond the length of the elements array {}",
251 elements.len()
252 );
253 }
254 })
255 } else {
256 vortex_bail!(
258 InvalidArgument: "offsets array with encoding {} must support min_max compute function",
259 offsets.encoding_id()
260 );
261 };
262
263 if let Some(validity_len) = validity.maybe_len() {
265 vortex_ensure_eq!(
266 validity_len,
267 offsets.len() - 1,
268 InvalidArgument: "validity length does not match array size",
269 );
270 }
271
272 Ok(())
273 }
274 }
279
280pub trait ListArrayExt: ListArraySlotsExt {
281 fn nullability(&self) -> crate::dtype::Nullability {
282 match self.as_ref().dtype() {
283 DType::List(_, nullability) => *nullability,
284 _ => unreachable!("ListArrayExt requires a list dtype"),
285 }
286 }
287
288 fn list_validity(&self) -> Validity {
289 child_to_validity(
290 self.as_ref().slots()[ListSlots::VALIDITY].as_ref(),
291 self.nullability(),
292 )
293 }
294
295 #[allow(clippy::disallowed_methods)]
296 fn offset_at(&self, index: usize) -> VortexResult<usize> {
297 vortex_ensure!(
298 index <= self.as_ref().len(),
299 "Index {index} out of bounds 0..={}",
300 self.as_ref().len()
301 );
302
303 if let Some(p) = self.offsets().as_opt::<Primitive>() {
304 Ok(match_each_native_ptype!(p.ptype(), |P| {
305 p.as_slice::<P>()[index].as_()
306 }))
307 } else {
308 self.offsets()
309 .execute_scalar(index, &mut legacy_session().create_execution_ctx())?
310 .as_primitive()
311 .as_::<usize>()
312 .ok_or_else(|| vortex_error::vortex_err!("offset value does not fit in usize"))
313 }
314 }
315
316 fn list_elements_at(&self, index: usize) -> VortexResult<ArrayRef> {
317 let start = self.offset_at(index)?;
318 let end = self.offset_at(index + 1)?;
319 self.elements().slice(start..end)
320 }
321
322 fn sliced_elements(&self) -> VortexResult<ArrayRef> {
323 let start = self.offset_at(0)?;
324 let end = self.offset_at(self.as_ref().len())?;
325 self.elements().slice(start..end)
326 }
327
328 fn element_dtype(&self) -> &DType {
329 self.elements().dtype()
330 }
331
332 fn reset_offsets(&self, recurse: bool, ctx: &mut ExecutionCtx) -> VortexResult<Array<List>> {
333 let mut elements = self.sliced_elements()?;
334 if recurse && elements.is_canonical() {
335 let compacted = elements
336 .execute::<Canonical>(ctx)?
337 .compact(ctx)?
338 .into_array();
339 elements = compacted;
340 } else if recurse && let Some(child_list_array) = elements.as_opt::<List>() {
341 elements = child_list_array
342 .into_owned()
343 .reset_offsets(recurse, ctx)?
344 .into_array();
345 }
346
347 let offsets = self.offsets();
348 let first_offset = offsets.execute_scalar(0, ctx)?;
349 let adjusted_offsets = offsets.clone().binary(
350 ConstantArray::new(first_offset, offsets.len()).into_array(),
351 Operator::Sub,
352 )?;
353
354 Ok(unsafe { ListArray::new_unchecked(elements, adjusted_offsets, self.list_validity()) })
356 }
357}
358impl<T: TypedArrayRef<List>> ListArrayExt for T {}
359
360impl Array<List> {
361 pub fn new(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
363 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
364 let len = offsets.len().saturating_sub(1);
365 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
366 let data = ListData::build(elements, offsets, validity);
367 unsafe { Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data, slots)) }
368 }
369
370 pub fn try_new(
372 elements: ArrayRef,
373 offsets: ArrayRef,
374 validity: Validity,
375 ) -> VortexResult<Self> {
376 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
377 let len = offsets.len().saturating_sub(1);
378 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
379 let data = ListData::try_build(elements, offsets, validity)?;
380 Ok(unsafe { Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data, slots)) })
381 }
382
383 pub unsafe fn new_unchecked(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
389 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
390 let len = offsets.len().saturating_sub(1);
391 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
392 let data = unsafe { ListData::new_unchecked() };
393 unsafe { Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data, slots)) }
394 }
395
396 pub fn into_data_parts(self) -> ListDataParts {
397 let dtype = self.dtype().clone();
398 let elements = self.slots()[ListSlots::ELEMENTS]
399 .clone()
400 .vortex_expect("ListArray elements slot");
401 let offsets = self.slots()[ListSlots::OFFSETS]
402 .clone()
403 .vortex_expect("ListArray offsets slot");
404 let validity = self.list_validity();
405 ListDataParts {
406 elements,
407 offsets,
408 validity,
409 dtype,
410 }
411 }
412}