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::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
42pub(super) const ELEMENTS_SLOT: usize = 0;
44pub(super) const OFFSETS_SLOT: usize = 1;
46pub(super) const VALIDITY_SLOT: usize = 2;
48pub(super) const NUM_SLOTS: usize = 3;
49pub(super) const SLOT_NAMES: [&str; NUM_SLOTS] = ["elements", "offsets", "validity"];
50
51#[derive(Clone, Debug, Default)]
105pub struct ListData;
106
107impl Display for ListData {
108 fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
109 Ok(())
110 }
111}
112
113pub struct ListDataParts {
114 pub elements: ArrayRef,
115 pub offsets: ArrayRef,
116 pub validity: Validity,
117 pub dtype: DType,
118}
119
120impl ListData {
121 pub(crate) fn make_slots(
122 elements: &ArrayRef,
123 offsets: &ArrayRef,
124 validity: &Validity,
125 len: usize,
126 ) -> ArraySlots {
127 smallvec![
128 Some(elements.clone()),
129 Some(offsets.clone()),
130 validity_to_child(validity, len),
131 ]
132 }
133
134 pub fn build(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
141 Self::try_build(elements, offsets, validity).vortex_expect("ListArray new")
142 }
143
144 pub(crate) fn try_build(
153 elements: ArrayRef,
154 offsets: ArrayRef,
155 validity: Validity,
156 ) -> VortexResult<Self> {
157 Self::validate(&elements, &offsets, &validity)?;
158
159 Ok(unsafe { Self::new_unchecked() })
161 }
162
163 pub unsafe fn new_unchecked() -> Self {
180 Self
181 }
182
183 #[allow(clippy::disallowed_methods)]
187 pub fn validate(
188 elements: &ArrayRef,
189 offsets: &ArrayRef,
190 validity: &Validity,
191 ) -> VortexResult<()> {
192 vortex_ensure!(
194 !offsets.is_empty(),
195 InvalidArgument: "Offsets must have at least one element, [0] for an empty list"
196 );
197
198 vortex_ensure!(
200 offsets.dtype().is_int() && !offsets.dtype().is_nullable(),
201 InvalidArgument: "offsets have invalid type {}",
202 offsets.dtype()
203 );
204
205 let offsets_ptype = offsets.dtype().as_ptype();
207 let mut ctx = legacy_session().create_execution_ctx();
208
209 if let Some(is_sorted) = offsets.statistics().compute_is_sorted(&mut ctx) {
211 vortex_ensure!(is_sorted, InvalidArgument: "offsets must be sorted");
212 } else {
213 vortex_bail!(InvalidArgument: "offsets must report is_sorted statistic");
214 }
215
216 if let Some(min_max) = min_max(offsets, &mut ctx, NumericalAggregateOpts::default())? {
219 match_each_integer_ptype!(offsets_ptype, |P| {
220 #[allow(clippy::absurd_extreme_comparisons, unused_comparisons)]
221 {
222 let max = min_max
223 .max
224 .as_primitive()
225 .as_::<P>()
226 .vortex_expect("offsets type must fit offsets values");
227 let min = min_max
228 .min
229 .as_primitive()
230 .as_::<P>()
231 .vortex_expect("offsets type must fit offsets values");
232
233 vortex_ensure!(
234 min >= 0,
235 InvalidArgument: "offsets minimum {min} outside valid range [0, {max}]"
236 );
237
238 vortex_ensure!(
239 max <= P::try_from(elements.len()).unwrap_or_else(|_| vortex_panic!(
240 "Offsets type {} must be able to fit elements length {}",
241 <P as NativePType>::PTYPE,
242 elements.len()
243 )),
244 InvalidArgument: "Max offset {max} is beyond the length of the elements array {}",
245 elements.len()
246 );
247 }
248 })
249 } else {
250 vortex_bail!(
252 InvalidArgument: "offsets array with encoding {} must support min_max compute function",
253 offsets.encoding_id()
254 );
255 };
256
257 if let Some(validity_len) = validity.maybe_len() {
259 vortex_ensure!(
260 validity_len == offsets.len() - 1,
261 InvalidArgument: "validity with size {validity_len} does not match array size {}",
262 offsets.len() - 1
263 );
264 }
265
266 Ok(())
267 }
268 }
273
274pub trait ListArrayExt: TypedArrayRef<List> {
275 fn nullability(&self) -> crate::dtype::Nullability {
276 match self.as_ref().dtype() {
277 DType::List(_, nullability) => *nullability,
278 _ => unreachable!("ListArrayExt requires a list dtype"),
279 }
280 }
281
282 fn elements(&self) -> &ArrayRef {
283 self.as_ref().slots()[ELEMENTS_SLOT]
284 .as_ref()
285 .vortex_expect("ListArray elements slot")
286 }
287
288 fn offsets(&self) -> &ArrayRef {
289 self.as_ref().slots()[OFFSETS_SLOT]
290 .as_ref()
291 .vortex_expect("ListArray offsets slot")
292 }
293
294 fn list_validity(&self) -> Validity {
295 child_to_validity(
296 self.as_ref().slots()[VALIDITY_SLOT].as_ref(),
297 self.nullability(),
298 )
299 }
300
301 #[allow(clippy::disallowed_methods)]
302 fn offset_at(&self, index: usize) -> VortexResult<usize> {
303 vortex_ensure!(
304 index <= self.as_ref().len(),
305 "Index {index} out of bounds 0..={}",
306 self.as_ref().len()
307 );
308
309 if let Some(p) = self.offsets().as_opt::<Primitive>() {
310 Ok(match_each_native_ptype!(p.ptype(), |P| {
311 p.as_slice::<P>()[index].as_()
312 }))
313 } else {
314 self.offsets()
315 .execute_scalar(index, &mut legacy_session().create_execution_ctx())?
316 .as_primitive()
317 .as_::<usize>()
318 .ok_or_else(|| vortex_error::vortex_err!("offset value does not fit in usize"))
319 }
320 }
321
322 fn list_elements_at(&self, index: usize) -> VortexResult<ArrayRef> {
323 let start = self.offset_at(index)?;
324 let end = self.offset_at(index + 1)?;
325 self.elements().slice(start..end)
326 }
327
328 fn sliced_elements(&self) -> VortexResult<ArrayRef> {
329 let start = self.offset_at(0)?;
330 let end = self.offset_at(self.as_ref().len())?;
331 self.elements().slice(start..end)
332 }
333
334 fn element_dtype(&self) -> &DType {
335 self.elements().dtype()
336 }
337
338 fn reset_offsets(&self, recurse: bool, ctx: &mut ExecutionCtx) -> VortexResult<Array<List>> {
339 let mut elements = self.sliced_elements()?;
340 if recurse && elements.is_canonical() {
341 let compacted = elements
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 Ok(unsafe { ListArray::new_unchecked(elements, adjusted_offsets, self.list_validity()) })
362 }
363}
364impl<T: TypedArrayRef<List>> ListArrayExt for T {}
365
366impl Array<List> {
367 pub fn new(elements: ArrayRef, offsets: ArrayRef, validity: Validity) -> Self {
369 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
370 let len = offsets.len().saturating_sub(1);
371 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
372 let data = ListData::build(elements, offsets, validity);
373 unsafe {
374 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
375 }
376 }
377
378 pub fn try_new(
380 elements: ArrayRef,
381 offsets: ArrayRef,
382 validity: Validity,
383 ) -> VortexResult<Self> {
384 let dtype = DType::List(Arc::new(elements.dtype().clone()), validity.nullability());
385 let len = offsets.len().saturating_sub(1);
386 let slots = ListData::make_slots(&elements, &offsets, &validity, len);
387 let data = ListData::try_build(elements, offsets, validity)?;
388 Ok(unsafe {
389 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
390 })
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 {
404 Array::from_parts_unchecked(ArrayParts::new(List, dtype, len, data).with_slots(slots))
405 }
406 }
407
408 pub fn into_data_parts(self) -> ListDataParts {
409 let dtype = self.dtype().clone();
410 let elements = self.slots()[ELEMENTS_SLOT]
411 .clone()
412 .vortex_expect("ListArray elements slot");
413 let offsets = self.slots()[OFFSETS_SLOT]
414 .clone()
415 .vortex_expect("ListArray offsets slot");
416 let validity = self.list_validity();
417 ListDataParts {
418 elements,
419 offsets,
420 validity,
421 dtype,
422 }
423 }
424}