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