vortex_array/arrays/list/
array.rs1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::sync::Arc;
7
8use num_traits::AsPrimitive;
9use smallvec::smallvec;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_bail;
13use vortex_error::vortex_ensure;
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::arrays::ConstantArray;
30use crate::arrays::List;
31use crate::arrays::Primitive;
32use crate::builtins::ArrayBuiltins;
33use crate::dtype::DType;
34use crate::dtype::NativePType;
35use crate::legacy_session;
36use crate::match_each_integer_ptype;
37use crate::match_each_native_ptype;
38use crate::scalar_fn::fns::operators::Operator;
39use crate::validity::Validity;
40
41pub(super) const ELEMENTS_SLOT: usize = 0;
43pub(super) const OFFSETS_SLOT: usize = 1;
45pub(super) const VALIDITY_SLOT: usize = 2;
47pub(super) const NUM_SLOTS: usize = 3;
48pub(super) const SLOT_NAMES: [&str; NUM_SLOTS] = ["elements", "offsets", "validity"];
49
50#[derive(Clone, Debug, Default)]
104pub struct ListData;
105
106impl Display for ListData {
107 fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
108 Ok(())
109 }
110}
111
112pub struct ListDataParts {
113 pub elements: ArrayRef,
114 pub offsets: ArrayRef,
115 pub validity: Validity,
116 pub dtype: DType,
117}
118
119impl ListData {
120 pub(crate) fn make_slots(
121 elements: &ArrayRef,
122 offsets: &ArrayRef,
123 validity: &Validity,
124 len: usize,
125 ) -> ArraySlots {
126 smallvec![
127 Some(elements.clone()),
128 Some(offsets.clone()),
129 validity_to_child(validity, len),
130 ]
131 }
132
133 pub fn build(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
140 Self::try_build(elements, offsets, validity).vortex_expect("ListArray new")
141 }
142
143 pub(crate) fn try_build(
152 elements: ArrayRef,
153 offsets: ArrayRef,
154 validity: Validity,
155 ) -> VortexResult<Self> {
156 Self::validate(&elements, &offsets, &validity)?;
157
158 Ok(unsafe { Self::new_unchecked() })
160 }
161
162 pub unsafe fn new_unchecked() -> Self {
179 Self
180 }
181
182 #[allow(clippy::disallowed_methods)]
186 pub fn validate(
187 elements: &ArrayRef,
188 offsets: &ArrayRef,
189 validity: &Validity,
190 ) -> VortexResult<()> {
191 vortex_ensure!(
193 !offsets.is_empty(),
194 InvalidArgument: "Offsets must have at least one element, [0] for an empty list"
195 );
196
197 vortex_ensure!(
199 offsets.dtype().is_int() && !offsets.dtype().is_nullable(),
200 InvalidArgument: "offsets have invalid type {}",
201 offsets.dtype()
202 );
203
204 let offsets_ptype = offsets.dtype().as_ptype();
206 let mut ctx = legacy_session().create_execution_ctx();
207
208 if let Some(is_sorted) = offsets.statistics().compute_is_sorted(&mut ctx) {
210 vortex_ensure!(is_sorted, InvalidArgument: "offsets must be sorted");
211 } else {
212 vortex_bail!(InvalidArgument: "offsets must report is_sorted statistic");
213 }
214
215 if let Some(min_max) = min_max(offsets, &mut ctx, NumericalAggregateOpts::default())? {
218 match_each_integer_ptype!(offsets_ptype, |P| {
219 #[allow(clippy::absurd_extreme_comparisons, unused_comparisons)]
220 {
221 let max = min_max
222 .max
223 .as_primitive()
224 .as_::<P>()
225 .vortex_expect("offsets type must fit offsets values");
226 let min = min_max
227 .min
228 .as_primitive()
229 .as_::<P>()
230 .vortex_expect("offsets type must fit offsets values");
231
232 vortex_ensure!(
233 min >= 0,
234 InvalidArgument: "offsets minimum {min} outside valid range [0, {max}]"
235 );
236
237 vortex_ensure!(
238 max <= P::try_from(elements.len()).unwrap_or_else(|_| vortex_panic!(
239 "Offsets type {} must be able to fit elements length {}",
240 <P as NativePType>::PTYPE,
241 elements.len()
242 )),
243 InvalidArgument: "Max offset {max} is beyond the length of the elements array {}",
244 elements.len()
245 );
246 }
247 })
248 } else {
249 vortex_bail!(
251 InvalidArgument: "offsets array with encoding {} must support min_max compute function",
252 offsets.encoding_id()
253 );
254 };
255
256 if let Some(validity_len) = validity.maybe_len() {
258 vortex_ensure!(
259 validity_len == offsets.len() - 1,
260 InvalidArgument: "validity with size {validity_len} does not match array size {}",
261 offsets.len() - 1
262 );
263 }
264
265 Ok(())
266 }
267 }
272
273pub trait ListArrayExt: TypedArrayRef<List> {
274 fn nullability(&self) -> crate::dtype::Nullability {
275 match self.as_ref().dtype() {
276 DType::List(_, nullability) => *nullability,
277 _ => unreachable!("ListArrayExt requires a list dtype"),
278 }
279 }
280
281 fn elements(&self) -> &ArrayRef {
282 self.as_ref().slots()[ELEMENTS_SLOT]
283 .as_ref()
284 .vortex_expect("ListArray elements slot")
285 }
286
287 fn offsets(&self) -> &ArrayRef {
288 self.as_ref().slots()[OFFSETS_SLOT]
289 .as_ref()
290 .vortex_expect("ListArray offsets slot")
291 }
292
293 fn list_validity(&self) -> Validity {
294 child_to_validity(
295 self.as_ref().slots()[VALIDITY_SLOT].as_ref(),
296 self.nullability(),
297 )
298 }
299
300 #[allow(clippy::disallowed_methods)]
301 fn offset_at(&self, index: usize) -> VortexResult<usize> {
302 vortex_ensure!(
303 index <= self.as_ref().len(),
304 "Index {index} out of bounds 0..={}",
305 self.as_ref().len()
306 );
307
308 if let Some(p) = self.offsets().as_opt::<Primitive>() {
309 Ok(match_each_native_ptype!(p.ptype(), |P| {
310 p.as_slice::<P>()[index].as_()
311 }))
312 } else {
313 self.offsets()
314 .execute_scalar(index, &mut legacy_session().create_execution_ctx())?
315 .as_primitive()
316 .as_::<usize>()
317 .ok_or_else(|| vortex_error::vortex_err!("offset value does not fit in usize"))
318 }
319 }
320
321 fn list_elements_at(&self, index: usize) -> VortexResult<ArrayRef> {
322 let start = self.offset_at(index)?;
323 let end = self.offset_at(index + 1)?;
324 self.elements().slice(start..end)
325 }
326
327 fn sliced_elements(&self) -> VortexResult<ArrayRef> {
328 let start = self.offset_at(0)?;
329 let end = self.offset_at(self.as_ref().len())?;
330 self.elements().slice(start..end)
331 }
332
333 fn element_dtype(&self) -> &DType {
334 self.elements().dtype()
335 }
336
337 fn reset_offsets(&self, recurse: bool, ctx: &mut ExecutionCtx) -> VortexResult<Array<List>> {
338 let mut elements = self.sliced_elements()?;
339 if recurse && elements.is_canonical() {
340 let compacted = elements
341 .clone()
342 .execute::<Canonical>(ctx)?
343 .compact(ctx)?
344 .into_array();
345 elements = compacted;
346 } else if recurse && let Some(child_list_array) = elements.as_opt::<List>() {
347 elements = child_list_array
348 .into_owned()
349 .reset_offsets(recurse, ctx)?
350 .into_array();
351 }
352
353 let offsets = self.offsets();
354 let first_offset = offsets.execute_scalar(0, ctx)?;
355 let adjusted_offsets = offsets.clone().binary(
356 ConstantArray::new(first_offset, offsets.len()).into_array(),
357 Operator::Sub,
358 )?;
359
360 Array::<List>::try_new(elements, adjusted_offsets, self.list_validity())
361 }
362}
363impl<T: TypedArrayRef<List>> ListArrayExt for T {}
364
365impl Array<List> {
366 pub fn new(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
368 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
369 let len = offsets.len().saturating_sub(1);
370 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
371 let data = ListData::build(elements, offsets, validity);
372 unsafe {
373 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
374 }
375 }
376
377 pub fn try_new(
379 elements: ArrayRef,
380 offsets: ArrayRef,
381 validity: Validity,
382 ) -> VortexResult<Self> {
383 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
384 let len = offsets.len().saturating_sub(1);
385 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
386 let data = ListData::try_build(elements, offsets, validity)?;
387 Ok(unsafe {
388 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
389 })
390 }
391
392 pub unsafe fn new_unchecked(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
398 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
399 let len = offsets.len().saturating_sub(1);
400 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
401 let data = unsafe { ListData::new_unchecked() };
402 unsafe {
403 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
404 }
405 }
406
407 pub fn into_data_parts(self) -> ListDataParts {
408 let dtype = self.dtype().clone();
409 let elements = self.slots()[ELEMENTS_SLOT]
410 .clone()
411 .vortex_expect("ListArray elements slot");
412 let offsets = self.slots()[OFFSETS_SLOT]
413 .clone()
414 .vortex_expect("ListArray offsets slot");
415 let validity = self.list_validity();
416 ListDataParts {
417 elements,
418 offsets,
419 validity,
420 dtype,
421 }
422 }
423}