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_panic;
14
15use crate::ArrayRef;
16use crate::ArraySlots;
17use crate::Canonical;
18use crate::ExecutionCtx;
19use crate::IntoArray;
20use crate::VortexSessionExecute;
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::legacy_session;
37use crate::match_each_integer_ptype;
38use crate::match_each_native_ptype;
39use crate::scalar_fn::fns::operators::Operator;
40use crate::validity::Validity;
41
42#[array_slots(List)]
43pub struct ListSlots {
44 #[slot(0)]
46 pub elements: ArrayRef,
47 #[slot(1)]
49 pub offsets: ArrayRef,
50 #[slot(2)]
52 pub validity: Option<ArrayRef>,
53}
54
55#[derive(Clone, Debug, Default)]
109pub struct ListData;
110
111impl Display for ListData {
112 fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
113 Ok(())
114 }
115}
116
117pub struct ListDataParts {
118 pub elements: ArrayRef,
119 pub offsets: ArrayRef,
120 pub validity: Validity,
121 pub dtype: DType,
122}
123
124impl ListData {
125 pub(crate) fn make_slots(
126 elements: &ArrayRef,
127 offsets: &ArrayRef,
128 validity: &Validity,
129 len: usize,
130 ) -> ArraySlots {
131 ListSlots {
132 elements: elements.clone(),
133 offsets: offsets.clone(),
134 validity: validity_to_child(validity, len),
135 }
136 .into_slots()
137 }
138
139 pub fn build(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
146 Self::try_build(elements, offsets, validity).vortex_expect("ListArray new")
147 }
148
149 pub(crate) fn try_build(
158 elements: ArrayRef,
159 offsets: ArrayRef,
160 validity: Validity,
161 ) -> VortexResult<Self> {
162 Self::validate(&elements, &offsets, &validity)?;
163
164 Ok(unsafe { Self::new_unchecked() })
166 }
167
168 pub unsafe fn new_unchecked() -> Self {
185 Self
186 }
187
188 #[allow(clippy::disallowed_methods)]
192 pub fn validate(
193 elements: &ArrayRef,
194 offsets: &ArrayRef,
195 validity: &Validity,
196 ) -> VortexResult<()> {
197 vortex_ensure!(
199 !offsets.is_empty(),
200 InvalidArgument: "Offsets must have at least one element, [0] for an empty list"
201 );
202
203 vortex_ensure!(
205 offsets.dtype().is_int() && !offsets.dtype().is_nullable(),
206 InvalidArgument: "offsets have invalid type {}",
207 offsets.dtype()
208 );
209
210 let offsets_ptype = offsets.dtype().as_ptype();
212 let mut ctx = legacy_session().create_execution_ctx();
213
214 if let Some(is_sorted) = offsets.statistics().compute_is_sorted(&mut ctx) {
216 vortex_ensure!(is_sorted, InvalidArgument: "offsets must be sorted");
217 } else {
218 vortex_bail!(InvalidArgument: "offsets must report is_sorted statistic");
219 }
220
221 if let Some(min_max) = min_max(offsets, &mut ctx, NumericalAggregateOpts::default())? {
224 match_each_integer_ptype!(offsets_ptype, |P| {
225 #[allow(clippy::absurd_extreme_comparisons, unused_comparisons)]
226 {
227 let max = min_max
228 .max
229 .as_primitive()
230 .as_::<P>()
231 .vortex_expect("offsets type must fit offsets values");
232 let min = min_max
233 .min
234 .as_primitive()
235 .as_::<P>()
236 .vortex_expect("offsets type must fit offsets values");
237
238 vortex_ensure!(
239 min >= 0,
240 InvalidArgument: "offsets minimum {min} outside valid range [0, {max}]"
241 );
242
243 vortex_ensure!(
244 max <= P::try_from(elements.len()).unwrap_or_else(|_| vortex_panic!(
245 "Offsets type {} must be able to fit elements length {}",
246 <P as NativePType>::PTYPE,
247 elements.len()
248 )),
249 InvalidArgument: "Max offset {max} is beyond the length of the elements array {}",
250 elements.len()
251 );
252 }
253 })
254 } else {
255 vortex_bail!(
257 InvalidArgument: "offsets array with encoding {} must support min_max compute function",
258 offsets.encoding_id()
259 );
260 };
261
262 if let Some(validity_len) = validity.maybe_len() {
264 vortex_ensure!(
265 validity_len == offsets.len() - 1,
266 InvalidArgument: "validity with size {validity_len} does not match array size {}",
267 offsets.len() - 1
268 );
269 }
270
271 Ok(())
272 }
273 }
278
279pub trait ListArrayExt: ListArraySlotsExt {
280 fn nullability(&self) -> crate::dtype::Nullability {
281 match self.as_ref().dtype() {
282 DType::List(_, nullability) => *nullability,
283 _ => unreachable!("ListArrayExt requires a list dtype"),
284 }
285 }
286
287 fn list_validity(&self) -> Validity {
288 child_to_validity(
289 self.as_ref().slots()[ListSlots::VALIDITY].as_ref(),
290 self.nullability(),
291 )
292 }
293
294 #[allow(clippy::disallowed_methods)]
295 fn offset_at(&self, index: usize) -> VortexResult<usize> {
296 vortex_ensure!(
297 index <= self.as_ref().len(),
298 "Index {index} out of bounds 0..={}",
299 self.as_ref().len()
300 );
301
302 if let Some(p) = self.offsets().as_opt::<Primitive>() {
303 Ok(match_each_native_ptype!(p.ptype(), |P| {
304 p.as_slice::<P>()[index].as_()
305 }))
306 } else {
307 self.offsets()
308 .execute_scalar(index, &mut legacy_session().create_execution_ctx())?
309 .as_primitive()
310 .as_::<usize>()
311 .ok_or_else(|| vortex_error::vortex_err!("offset value does not fit in usize"))
312 }
313 }
314
315 fn list_elements_at(&self, index: usize) -> VortexResult<ArrayRef> {
316 let start = self.offset_at(index)?;
317 let end = self.offset_at(index + 1)?;
318 self.elements().slice(start..end)
319 }
320
321 fn sliced_elements(&self) -> VortexResult<ArrayRef> {
322 let start = self.offset_at(0)?;
323 let end = self.offset_at(self.as_ref().len())?;
324 self.elements().slice(start..end)
325 }
326
327 fn element_dtype(&self) -> &DType {
328 self.elements().dtype()
329 }
330
331 fn reset_offsets(&self, recurse: bool, ctx: &mut ExecutionCtx) -> VortexResult<Array<List>> {
332 let mut elements = self.sliced_elements()?;
333 if recurse && elements.is_canonical() {
334 let compacted = elements
335 .execute::<Canonical>(ctx)?
336 .compact(ctx)?
337 .into_array();
338 elements = compacted;
339 } else if recurse && let Some(child_list_array) = elements.as_opt::<List>() {
340 elements = child_list_array
341 .into_owned()
342 .reset_offsets(recurse, ctx)?
343 .into_array();
344 }
345
346 let offsets = self.offsets();
347 let first_offset = offsets.execute_scalar(0, ctx)?;
348 let adjusted_offsets = offsets.clone().binary(
349 ConstantArray::new(first_offset, offsets.len()).into_array(),
350 Operator::Sub,
351 )?;
352
353 Ok(unsafe { ListArray::new_unchecked(elements, adjusted_offsets, self.list_validity()) })
355 }
356}
357impl<T: TypedArrayRef<List>> ListArrayExt for T {}
358
359impl Array<List> {
360 pub fn new(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
362 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
363 let len = offsets.len().saturating_sub(1);
364 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
365 let data = ListData::build(elements, offsets, validity);
366 unsafe {
367 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
368 }
369 }
370
371 pub fn try_new(
373 elements: ArrayRef,
374 offsets: ArrayRef,
375 validity: Validity,
376 ) -> VortexResult<Self> {
377 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
378 let len = offsets.len().saturating_sub(1);
379 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
380 let data = ListData::try_build(elements, offsets, validity)?;
381 Ok(unsafe {
382 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
383 })
384 }
385
386 pub unsafe fn new_unchecked(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
392 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
393 let len = offsets.len().saturating_sub(1);
394 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
395 let data = unsafe { ListData::new_unchecked() };
396 unsafe {
397 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
398 }
399 }
400
401 pub fn into_data_parts(self) -> ListDataParts {
402 let dtype = self.dtype().clone();
403 let elements = self.slots()[ListSlots::ELEMENTS]
404 .clone()
405 .vortex_expect("ListArray elements slot");
406 let offsets = self.slots()[ListSlots::OFFSETS]
407 .clone()
408 .vortex_expect("ListArray offsets slot");
409 let validity = self.list_validity();
410 ListDataParts {
411 elements,
412 offsets,
413 validity,
414 dtype,
415 }
416 }
417}