1use num_traits::AsPrimitive;
5use vortex_error::VortexResult;
6use vortex_error::vortex_bail;
7use vortex_session::VortexSession;
8use vortex_session::registry::CachedId;
9
10use crate::ArrayRef;
11use crate::ExecutionCtx;
12use crate::IntoArray;
13use crate::array::ArrayView;
14use crate::arrays::ConstantArray;
15use crate::arrays::FixedSizeList;
16use crate::arrays::List;
17use crate::arrays::ListView;
18use crate::arrays::fixed_size_list::FixedSizeListArrayExt;
19use crate::arrays::list::ListArrayExt;
20use crate::arrays::list::ListArraySlotsExt;
21use crate::arrays::listview::ListViewArrayExt;
22use crate::arrays::listview::ListViewArraySlotsExt;
23use crate::builtins::ArrayBuiltins;
24use crate::dtype::DType;
25use crate::dtype::Nullability;
26use crate::dtype::PType;
27use crate::matcher::Matcher;
28use crate::scalar::Scalar;
29use crate::scalar_fn::Arity;
30use crate::scalar_fn::ChildName;
31use crate::scalar_fn::EmptyOptions;
32use crate::scalar_fn::ExecutionArgs;
33use crate::scalar_fn::ScalarFnId;
34use crate::scalar_fn::ScalarFnVTable;
35use crate::scalar_fn::fns::operators::Operator;
36
37#[derive(Clone)]
43pub struct ListLength;
44
45impl ScalarFnVTable for ListLength {
46 type Options = EmptyOptions;
47
48 fn id(&self) -> ScalarFnId {
49 static ID: CachedId = CachedId::new("vortex.list.length");
50 *ID
51 }
52
53 fn serialize(&self, _instance: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
54 Ok(Some(vec![]))
55 }
56
57 fn deserialize(
58 &self,
59 _metadata: &[u8],
60 _session: &VortexSession,
61 ) -> VortexResult<Self::Options> {
62 Ok(EmptyOptions)
63 }
64
65 fn arity(&self, _options: &Self::Options) -> Arity {
66 Arity::Exact(1)
67 }
68
69 fn child_name(&self, _instance: &Self::Options, child_idx: usize) -> ChildName {
70 match child_idx {
71 0 => ChildName::from("input"),
72 _ => unreachable!("Invalid child index {child_idx} for list_length()"),
73 }
74 }
75
76 fn return_dtype(&self, _options: &Self::Options, arg_dtypes: &[DType]) -> VortexResult<DType> {
77 match &arg_dtypes[0] {
78 DType::List(_, nullable) | DType::FixedSizeList(_, _, nullable) => {
79 Ok(DType::Primitive(PType::U64, *nullable))
80 }
81 other => vortex_bail!("list_length() requires List or FixedSizeList, got {other}"),
82 }
83 }
84
85 fn execute(
86 &self,
87 _options: &Self::Options,
88 args: &dyn ExecutionArgs,
89 ctx: &mut ExecutionCtx,
90 ) -> VortexResult<ArrayRef> {
91 let input = args.get(0)?;
92 let nullability = input.dtype().nullability();
93
94 if let Some(scalar) = input.as_constant() {
95 let len_scalar = scalar_list_length(&scalar, nullability)?;
96 return Ok(ConstantArray::new(len_scalar, args.row_count()).into_array());
97 }
98
99 list_length(&input, nullability, ctx)
100 }
101
102 fn is_strict(&self, _options: &Self::Options) -> bool {
103 true
106 }
107
108 fn is_infallible(&self, _options: &Self::Options) -> bool {
109 true
110 }
111}
112
113fn scalar_list_length(scalar: &Scalar, nullability: Nullability) -> VortexResult<Scalar> {
114 if scalar.is_null() {
115 let dtype = DType::Primitive(PType::U64, Nullability::Nullable);
116 return Ok(Scalar::null(dtype));
117 }
118 let len: u64 = scalar.as_list().len().as_();
119 Ok(Scalar::primitive(len, nullability))
120}
121
122pub(crate) fn list_length(
123 array: &ArrayRef,
124 nullability: Nullability,
125 ctx: &mut ExecutionCtx,
126) -> VortexResult<ArrayRef> {
127 let any_list = array.clone().execute_until::<AnyList>(ctx)?;
128
129 let (lengths, validity) = if let Some(fsl) = any_list.as_opt::<FixedSizeList>() {
130 let size = fsl.list_size() as u64;
132 let lengths =
133 ConstantArray::new(Scalar::primitive(size, Nullability::NonNullable), fsl.len())
134 .into_array();
135 (lengths, fsl.validity()?)
136 } else if let Some(lv) = any_list.as_opt::<ListView>() {
137 (lv.sizes().clone(), lv.listview_validity())
139 } else if let Some(l) = any_list.as_opt::<List>() {
140 let lengths = list_length_from_offsets(l)?;
141 (lengths, l.list_validity())
142 } else {
143 let dtype = any_list.dtype();
144 vortex_bail!("list_length() requires List, ListView, or FixedSizeList but got {dtype}")
145 };
146
147 let len = lengths.len();
149 let lengths = lengths.cast(DType::Primitive(PType::U64, nullability))?;
150
151 if matches!(nullability, Nullability::Nullable) {
153 lengths.mask(validity.to_array(len))
154 } else {
155 Ok(lengths)
156 }
157}
158
159fn list_length_from_offsets(list: ArrayView<'_, List>) -> VortexResult<ArrayRef> {
162 let offsets = list.offsets();
163 let n = offsets.len().saturating_sub(1);
164
165 offsets
166 .slice(1..offsets.len())?
167 .binary(offsets.slice(0..n)?, Operator::Sub)
168}
169
170struct AnyList;
172
173impl Matcher for AnyList {
174 type Match<'a> = ();
175
176 fn try_match(array: &ArrayRef) -> Option<Self::Match<'_>> {
177 (array.as_opt::<List>().is_some()
178 || array.as_opt::<ListView>().is_some()
179 || array.as_opt::<FixedSizeList>().is_some())
180 .then_some(())
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use std::sync::Arc;
187
188 use rstest::rstest;
189 use vortex_buffer::buffer;
190 use vortex_error::VortexResult;
191
192 use crate::ArrayRef;
193 use crate::IntoArray;
194 use crate::VortexSessionExecute;
195 use crate::array_session;
196 use crate::arrays::BoolArray;
197 use crate::arrays::ConstantArray;
198 use crate::arrays::FixedSizeListArray;
199 use crate::arrays::ListArray;
200 use crate::arrays::ListViewArray;
201 use crate::arrays::PrimitiveArray;
202 use crate::assert_arrays_eq;
203 use crate::dtype::DType;
204 use crate::dtype::Nullability;
205 use crate::dtype::PType;
206 use crate::expr::cast;
207 use crate::expr::list_length;
208 use crate::expr::root;
209 use crate::scalar::Scalar;
210 use crate::validity::Validity;
211
212 fn create_list_elements() -> ArrayRef {
213 PrimitiveArray::from_option_iter::<i32, _>([
214 Some(1),
215 Some(2),
216 Some(3),
217 Some(4),
218 Some(5),
219 Some(6),
220 None,
221 ])
222 .into_array()
223 }
224
225 #[rstest]
226 #[case(buffer![0u32, 2, 5, 5, 7].into_array())]
227 #[case(buffer![0u64, 2, 5, 5, 7].into_array())]
228 fn test_list_length(#[case] offsets: ArrayRef) -> VortexResult<()> {
229 let elements = create_list_elements();
230 let list = ListArray::try_new(elements, offsets, Validity::NonNullable)?.into_array();
231 let result = list.apply(&list_length(root()))?;
232 let mut ctx = array_session().create_execution_ctx();
233 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 3, 0, 2]), &mut ctx);
234 Ok(())
235 }
236
237 #[rstest]
238 #[case(buffer![0u32, 2, 5, 5, 7].into_array())]
239 #[case(buffer![0u64, 2, 5, 5, 7].into_array())]
240 fn test_nullable_list_length(#[case] offsets: ArrayRef) -> VortexResult<()> {
241 let elements = create_list_elements();
242 let list = ListArray::try_new(
243 elements,
244 offsets,
245 Validity::Array(BoolArray::from_iter([true, false, true, false]).into_array()),
246 )?
247 .into_array();
248 let result = list.apply(&list_length(root()))?;
249
250 let mut ctx = array_session().create_execution_ctx();
251 let result = result.execute::<PrimitiveArray>(&mut ctx)?;
252
253 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(0), None]);
254
255 assert_arrays_eq!(result, expected, &mut ctx);
256
257 Ok(())
258 }
259
260 #[test]
261 fn test_null_scalar_list_length() -> VortexResult<()> {
262 let null_scalar = Scalar::null(DType::List(
263 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
264 Nullability::Nullable,
265 ));
266 let array = ConstantArray::new(null_scalar, 2).into_array();
267 let result = array.apply(&list_length(root()))?;
268
269 let mut ctx = array_session().create_execution_ctx();
270 assert!(!result.is_valid(0, &mut ctx)?);
271 assert!(!result.is_valid(1, &mut ctx)?);
272 Ok(())
273 }
274
275 #[test]
276 fn test_listview_length() -> VortexResult<()> {
277 let elements = create_list_elements();
278 let lv = ListViewArray::new(
279 elements,
280 buffer![5u32, 0, 4, 1].into_array(),
281 buffer![2u32, 3, 0, 2].into_array(),
282 Validity::NonNullable,
283 )
284 .into_array();
285 let result = lv.apply(&list_length(root()))?;
286 let mut ctx = array_session().create_execution_ctx();
287 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 3, 0, 2]), &mut ctx);
288 Ok(())
289 }
290
291 #[test]
292 fn test_listview_length_nullable() -> VortexResult<()> {
293 let elements = create_list_elements();
294 let lv = ListViewArray::new(
295 elements,
296 buffer![5u32, 0, 4, 1].into_array(),
297 buffer![2u32, 3, 0, 2].into_array(),
298 Validity::Array(BoolArray::from_iter([true, false, true, false]).into_array()),
299 )
300 .into_array();
301 let result = lv.apply(&list_length(root()))?;
302
303 let mut ctx = array_session().create_execution_ctx();
304 let result = result.execute::<PrimitiveArray>(&mut ctx)?;
305
306 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(0), None]);
307 assert_arrays_eq!(result, expected, &mut ctx);
308 Ok(())
309 }
310
311 #[test]
312 fn test_list_length_take() -> VortexResult<()> {
313 let elements = create_list_elements();
314 let list = ListArray::try_new(
315 elements,
316 buffer![0u32, 2, 5, 5, 7].into_array(),
317 Validity::NonNullable,
318 )?
319 .into_array();
320 let taken = list.take(buffer![3u64, 0, 2].into_array())?;
321
322 let result = taken.apply(&list_length(root()))?;
323 let mut ctx = array_session().create_execution_ctx();
324 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 2, 0]), &mut ctx);
325 Ok(())
326 }
327
328 fn create_fixed_size_list(validity: Validity) -> ArrayRef {
329 let elements = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5, 6, 7, 8]).into_array();
331 FixedSizeListArray::new(elements, 2, validity, 4).into_array()
332 }
333
334 #[test]
335 fn test_fixed_size_list_length() -> VortexResult<()> {
336 let fsl = create_fixed_size_list(Validity::NonNullable);
337 let result = fsl.apply(&list_length(root()))?;
338
339 let mut ctx = array_session().create_execution_ctx();
340 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 2, 2, 2]), &mut ctx);
341 Ok(())
342 }
343
344 #[test]
345 fn test_fixed_size_list_length_nullable() -> VortexResult<()> {
346 let fsl = create_fixed_size_list(Validity::Array(
347 BoolArray::from_iter([true, false, true, false]).into_array(),
348 ));
349 let result = fsl.apply(&list_length(root()))?;
350
351 let mut ctx = array_session().create_execution_ctx();
352 let result = result.execute::<PrimitiveArray>(&mut ctx)?;
353
354 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(2), None]);
355 assert_arrays_eq!(result, expected, &mut ctx);
356 Ok(())
357 }
358
359 #[test]
360 fn test_fallible_child_expression_fails() -> VortexResult<()> {
361 let fsl = create_fixed_size_list(Validity::Array(
362 BoolArray::from_iter([true, false, true, false]).into_array(),
363 ));
364 let failing_cast_dtype = DType::FixedSizeList(
365 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
366 2,
367 Nullability::NonNullable,
368 );
369
370 let lengths = fsl.apply(&list_length(cast(root(), failing_cast_dtype)))?;
371
372 let mut ctx = array_session().create_execution_ctx();
373 let result = lengths.execute::<ArrayRef>(&mut ctx);
374
375 assert!(result.is_err());
376
377 let err_message = result.unwrap_err().to_string();
378
379 assert!(
380 err_message.contains("Cannot cast array with invalid values to non-nullable type.")
381 );
382
383 Ok(())
384 }
385
386 #[test]
387 fn test_display() {
388 let expr = list_length(root());
389 assert_eq!(expr.to_string(), "vortex.list.length($)");
390 }
391}