1use std::mem::MaybeUninit;
5use std::ops::BitAnd;
6use std::ops::BitOr;
7use std::ops::Not;
8
9use vortex_buffer::Buffer;
10use vortex_buffer::BufferMut;
11use vortex_error::VortexExpect;
12use vortex_error::VortexResult;
13use vortex_mask::Mask;
14
15use crate::ArrayRef;
16use crate::ExecutionCtx;
17use crate::IntoArray;
18use crate::array::ArrayView;
19use crate::arrays::Chunked;
20use crate::arrays::ChunkedArray;
21use crate::arrays::ListView;
22use crate::arrays::ListViewArray;
23use crate::arrays::chunked::ChunkedArrayExt;
24use crate::arrays::listview::ListViewArraySlotsExt;
25use crate::builtins::ArrayBuiltins;
26use crate::dtype::DType;
27use crate::dtype::Nullability;
28use crate::dtype::PType;
29use crate::scalar_fn::fns::zip::ZipKernel;
30use crate::validity::Validity;
31
32impl ZipKernel for ListView {
42 fn zip(
43 if_true: ArrayView<'_, ListView>,
44 if_false: &ArrayRef,
45 mask: &ArrayRef,
46 ctx: &mut ExecutionCtx,
47 ) -> VortexResult<Option<ArrayRef>> {
48 let Some(if_false) = if_false.as_opt::<ListView>() else {
49 return Ok(None);
50 };
51
52 let mask = mask.clone().fill_null(false)?.execute::<Mask>(ctx)?;
54 match &mask {
55 Mask::AllTrue(_) | Mask::AllFalse(_) => return Ok(None),
57 Mask::Values(_) => {}
58 }
59
60 let len = if_true.len();
61
62 let result_elements_dtype = if_true
63 .elements()
64 .dtype()
65 .union_nullability(if_false.elements().dtype().nullability());
66
67 let true_elements = if_true.elements().cast(result_elements_dtype.clone())?;
70 let false_elements = if_false.elements().cast(result_elements_dtype.clone())?;
71
72 let false_shift = true_elements.len() as u64;
74
75 let mut chunks = Vec::with_capacity(2);
79 push_element_chunks(true_elements, &mut chunks);
80 push_element_chunks(false_elements, &mut chunks);
81 let elements = ChunkedArray::try_new(chunks, result_elements_dtype)?.into_array();
82
83 let true_offsets = to_u64(if_true.offsets(), ctx)?;
84 let true_sizes = to_u64(if_true.sizes(), ctx)?;
85 let false_offsets = to_u64(if_false.offsets(), ctx)?;
86 let false_sizes = to_u64(if_false.sizes(), ctx)?;
87
88 let mut offsets = BufferMut::<u64>::with_capacity(len);
89 let mut sizes = BufferMut::<u64>::with_capacity(len);
90 {
91 let true_offsets = true_offsets.as_slice();
92 let true_sizes = true_sizes.as_slice();
93 let false_offsets = false_offsets.as_slice();
94 let false_sizes = false_sizes.as_slice();
95
96 let offsets_out = offsets.spare_capacity_mut();
97 let sizes_out = sizes.spare_capacity_mut();
98
99 let mask_bits = mask
105 .values()
106 .vortex_expect("mask is Mask::Values")
107 .bit_buffer();
108 let unaligned = mask_bits.unaligned_chunks();
109 let lead = unaligned.lead_padding();
112
113 let mut select_block = |word: u64, base: usize, n: usize| {
114 let end = base + n;
115 select_column(
118 word,
119 &true_offsets[base..end],
120 &false_offsets[base..end],
121 false_shift,
122 &mut offsets_out[base..end],
123 );
124 select_column(
125 word,
126 &true_sizes[base..end],
127 &false_sizes[base..end],
128 0,
129 &mut sizes_out[base..end],
130 );
131 };
132
133 let mut base = 0;
134 if let Some(prefix) = unaligned.prefix() {
135 let n = (64 - lead).min(len);
136 select_block(prefix >> lead, base, n);
137 base += n;
138 }
139 for &word in unaligned.chunks() {
140 select_block(word, base, 64);
141 base += 64;
142 }
143 if let Some(suffix) = unaligned.suffix() {
144 select_block(suffix, base, len - base);
145 }
146 }
147
148 unsafe {
150 offsets.set_len(len);
151 sizes.set_len(len);
152 }
153
154 let validity = zip_validity(if_true.validity()?, if_false.validity()?, &mask, ctx)?;
155
156 Ok(Some(
157 ListViewArray::try_new(
158 elements,
159 offsets.freeze().into_array(),
160 sizes.freeze().into_array(),
161 validity,
162 )?
163 .into_array(),
164 ))
165 }
166}
167
168#[inline]
175fn select_column(
176 word: u64,
177 true_vals: &[u64],
178 false_vals: &[u64],
179 false_add: u64,
180 out: &mut [MaybeUninit<u64>],
181) {
182 let n = out.len();
183 let true_vals = &true_vals[..n];
184 let false_vals = &false_vals[..n];
185 for j in 0..n {
186 let lane = 0u64.wrapping_sub((word >> j) & 1);
188 out[j].write((true_vals[j] & lane) | ((false_vals[j] + false_add) & !lane));
189 }
190}
191
192fn push_element_chunks(array: ArrayRef, chunks: &mut Vec<ArrayRef>) {
195 match array.as_opt::<Chunked>() {
196 Some(chunked) => chunks.extend(chunked.iter_chunks().cloned()),
197 None => chunks.push(array),
198 }
199}
200
201fn to_u64(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Buffer<u64>> {
203 array
204 .clone()
205 .cast(DType::Primitive(PType::U64, Nullability::NonNullable))?
206 .execute::<Buffer<u64>>(ctx)
207}
208
209fn zip_validity(
212 if_true: Validity,
213 if_false: Validity,
214 mask: &Mask,
215 ctx: &mut ExecutionCtx,
216) -> VortexResult<Validity> {
217 Ok(match (&if_true, &if_false) {
218 (Validity::NonNullable, Validity::NonNullable) => Validity::NonNullable,
219 (Validity::AllValid, Validity::AllValid) => Validity::AllValid,
220 (Validity::AllInvalid, Validity::AllInvalid) => Validity::AllInvalid,
221 _ => {
222 let true_mask = if_true.execute_mask(mask.len(), ctx)?;
223 let false_mask = if_false.execute_mask(mask.len(), ctx)?;
224 let combined = true_mask
225 .bitand(mask)
226 .bitor(&false_mask.bitand(&mask.not()));
227 Validity::from_mask(combined, if_true.nullability() | if_false.nullability())
228 }
229 })
230}
231
232#[cfg(test)]
233mod tests {
234 #![allow(
235 clippy::cast_possible_truncation,
236 reason = "test fixtures use small indices that fit the target widths"
237 )]
238
239 use vortex_buffer::Buffer;
240 use vortex_buffer::buffer;
241 use vortex_error::VortexResult;
242 use vortex_mask::Mask;
243
244 use crate::ArrayRef;
245 use crate::IntoArray;
246 use crate::VortexSessionExecute;
247 use crate::array_session;
248 use crate::arrays::BoolArray;
249 use crate::arrays::Chunked;
250 use crate::arrays::ChunkedArray;
251 use crate::arrays::ListView;
252 use crate::arrays::ListViewArray;
253 use crate::arrays::chunked::ChunkedArrayExt;
254 use crate::arrays::listview::ListViewArraySlotsExt;
255 use crate::assert_arrays_eq;
256 use crate::builtins::ArrayBuiltins;
257 use crate::dtype::DType;
258 use crate::dtype::Nullability;
259 use crate::dtype::PType;
260 use crate::validity::Validity;
261
262 fn list_view(
263 elements: ArrayRef,
264 offsets: ArrayRef,
265 sizes: ArrayRef,
266 validity: Validity,
267 ) -> ArrayRef {
268 ListViewArray::try_new(elements, offsets, sizes, validity)
269 .unwrap()
270 .into_array()
271 }
272
273 #[test]
275 fn zip_selects_lists() -> VortexResult<()> {
276 let mut ctx = array_session().create_execution_ctx();
277 let if_true = list_view(
279 buffer![1i32, 2, 3, 4, 5, 6].into_array(),
280 buffer![0u32, 2, 3].into_array(),
281 buffer![2u32, 1, 3].into_array(),
282 Validity::NonNullable,
283 );
284 let if_false = list_view(
286 buffer![10i32, 20, 21, 30].into_array(),
287 buffer![0u32, 1, 3].into_array(),
288 buffer![1u32, 2, 1].into_array(),
289 Validity::NonNullable,
290 );
291 let mask = Mask::from_iter([true, false, true]);
292
293 let result = mask
294 .into_array()
295 .zip(if_true, if_false)?
296 .execute::<ArrayRef>(&mut ctx)?;
297
298 assert!(result.is::<ListView>());
300
301 let expected = list_view(
303 buffer![1i32, 2, 20, 21, 4, 5, 6].into_array(),
304 buffer![0u32, 2, 4].into_array(),
305 buffer![2u32, 2, 3].into_array(),
306 Validity::NonNullable,
307 );
308 assert_arrays_eq!(result, expected, &mut ctx);
309 Ok(())
310 }
311
312 #[test]
314 fn zip_selects_validity() -> VortexResult<()> {
315 let mut ctx = array_session().create_execution_ctx();
316 let if_true = list_view(
318 buffer![1i32, 2].into_array(),
319 buffer![0u32, 1, 1].into_array(),
320 buffer![1u32, 0, 1].into_array(),
321 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
322 );
323 let if_false = list_view(
325 buffer![10i32, 20].into_array(),
326 buffer![0u32, 1, 2].into_array(),
327 buffer![1u32, 1, 0].into_array(),
328 Validity::Array(BoolArray::from_iter([true, true, false]).into_array()),
329 );
330 let mask = Mask::from_iter([false, true, true]);
332
333 let result = mask.into_array().zip(if_true, if_false)?;
334
335 let expected = list_view(
337 buffer![10i32, 2].into_array(),
338 buffer![0u32, 1, 1].into_array(),
339 buffer![1u32, 0, 1].into_array(),
340 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
341 );
342 assert_arrays_eq!(result, expected, &mut ctx);
343 Ok(())
344 }
345
346 #[test]
349 fn zip_out_of_order_offsets_and_widening() -> VortexResult<()> {
350 let mut ctx = array_session().create_execution_ctx();
351 let if_true = list_view(
353 buffer![7i32, 8, 9, 5, 6].into_array(),
354 buffer![3u32, 0, 1].into_array(),
355 buffer![2u32, 1, 2].into_array(),
356 Validity::NonNullable,
357 );
358 let if_false = list_view(
360 buffer![100i32, 200, 201].into_array(),
361 buffer![0u32, 1, 1].into_array(),
362 buffer![1u32, 0, 2].into_array(),
363 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
364 );
365 let mask = Mask::from_iter([true, true, false]);
366
367 let result = mask
368 .into_array()
369 .zip(if_true, if_false)?
370 .execute::<ArrayRef>(&mut ctx)?;
371 assert!(result.is::<ListView>());
372
373 let expected = list_view(
375 buffer![5i32, 6, 7, 200, 201].into_array(),
376 buffer![0u32, 2, 3].into_array(),
377 buffer![2u32, 1, 2].into_array(),
378 Validity::AllValid,
379 );
380 assert_arrays_eq!(result, expected, &mut ctx);
381 Ok(())
382 }
383
384 #[test]
387 fn zip_spans_multiple_mask_chunks() -> VortexResult<()> {
388 let mut ctx = array_session().create_execution_ctx();
389 let len = 130usize;
391 let true_elements: Vec<i32> = (0..len as i32).collect();
392 let false_elements: Vec<i32> = (0..len as i32).map(|i| 1000 + i).collect();
393 let offsets: Vec<u64> = (0..len as u64).collect();
394 let sizes: Vec<u64> = vec![1; len];
395
396 let if_true = list_view(
397 true_elements
398 .iter()
399 .copied()
400 .collect::<Buffer<i32>>()
401 .into_array(),
402 offsets
403 .iter()
404 .copied()
405 .collect::<Buffer<u64>>()
406 .into_array(),
407 sizes.iter().copied().collect::<Buffer<u64>>().into_array(),
408 Validity::NonNullable,
409 );
410 let if_false = list_view(
411 false_elements
412 .iter()
413 .copied()
414 .collect::<Buffer<i32>>()
415 .into_array(),
416 offsets
417 .iter()
418 .copied()
419 .collect::<Buffer<u64>>()
420 .into_array(),
421 sizes.iter().copied().collect::<Buffer<u64>>().into_array(),
422 Validity::NonNullable,
423 );
424
425 let mask_bits: Vec<bool> = (0..len).map(|i| i.is_multiple_of(3) || i == 64).collect();
427 let mask = Mask::from_iter(mask_bits.iter().copied());
428
429 let result = mask
430 .into_array()
431 .zip(if_true, if_false)?
432 .execute::<ArrayRef>(&mut ctx)?;
433 assert!(result.is::<ListView>());
434
435 let expected_elements: Vec<i32> = (0..len)
437 .map(|i| {
438 if mask_bits[i] {
439 i as i32
440 } else {
441 1000 + i as i32
442 }
443 })
444 .collect();
445 let expected = list_view(
446 expected_elements
447 .iter()
448 .copied()
449 .collect::<Buffer<i32>>()
450 .into_array(),
451 offsets
452 .iter()
453 .copied()
454 .collect::<Buffer<u64>>()
455 .into_array(),
456 sizes.iter().copied().collect::<Buffer<u64>>().into_array(),
457 Validity::NonNullable,
458 );
459 assert_arrays_eq!(result, expected, &mut ctx);
460 Ok(())
461 }
462
463 #[test]
467 fn zip_handles_offset_mask() -> VortexResult<()> {
468 let mut ctx = array_session().create_execution_ctx();
469 let len = 200usize;
473 let true_elements: Vec<i32> = (0..len as i32).collect();
474 let false_elements: Vec<i32> = (0..len as i32).map(|i| 1000 + i).collect();
475 let offsets: Vec<u64> = (0..len as u64).collect();
476 let sizes: Vec<u64> = vec![1; len];
477
478 let single_element_view = |elements: &[i32]| {
479 list_view(
480 elements
481 .iter()
482 .copied()
483 .collect::<Buffer<i32>>()
484 .into_array(),
485 offsets
486 .iter()
487 .copied()
488 .collect::<Buffer<u64>>()
489 .into_array(),
490 sizes.iter().copied().collect::<Buffer<u64>>().into_array(),
491 Validity::NonNullable,
492 )
493 };
494 let if_true = single_element_view(&true_elements);
495 let if_false = single_element_view(&false_elements);
496
497 let offset = 3usize;
501 let mask_bits: Vec<bool> = (0..offset + len)
502 .map(|i| i.is_multiple_of(3) || i == offset + 64)
503 .collect();
504 let mask = BoolArray::from_iter(mask_bits.iter().copied())
505 .into_array()
506 .slice(offset..offset + len)?;
507
508 let result = mask.zip(if_true, if_false)?.execute::<ArrayRef>(&mut ctx)?;
509 assert!(result.is::<ListView>());
510
511 let expected_elements: Vec<i32> = (0..len)
513 .map(|i| {
514 if mask_bits[offset + i] {
515 i as i32
516 } else {
517 1000 + i as i32
518 }
519 })
520 .collect();
521 let expected = single_element_view(&expected_elements);
522 assert_arrays_eq!(result, expected, &mut ctx);
523 Ok(())
524 }
525
526 #[test]
529 fn zip_flattens_chunked_elements() -> VortexResult<()> {
530 let mut ctx = array_session().create_execution_ctx();
531 let chunked_elements = ChunkedArray::try_new(
533 vec![buffer![1i32, 2].into_array(), buffer![3i32].into_array()],
534 DType::Primitive(PType::I32, Nullability::NonNullable),
535 )?
536 .into_array();
537 let if_true = list_view(
538 chunked_elements,
539 buffer![0u32, 2].into_array(),
540 buffer![2u32, 1].into_array(),
541 Validity::NonNullable,
542 );
543 let if_false = list_view(
545 buffer![10i32, 20].into_array(),
546 buffer![0u32, 1].into_array(),
547 buffer![1u32, 1].into_array(),
548 Validity::NonNullable,
549 );
550 let mask = Mask::from_iter([true, false]);
551
552 let result = mask
553 .into_array()
554 .zip(if_true, if_false)?
555 .execute::<ArrayRef>(&mut ctx)?;
556
557 let result_lv = result
559 .as_opt::<ListView>()
560 .expect("zip keeps the list-view encoding");
561 let chunked = result_lv
562 .elements()
563 .as_opt::<Chunked>()
564 .expect("zip concatenates elements into a chunked array");
565 assert!(
566 chunked.iter_chunks().all(|chunk| !chunk.is::<Chunked>()),
567 "chunked elements must be flattened, not nested",
568 );
569
570 let expected = list_view(
572 buffer![1i32, 2, 20].into_array(),
573 buffer![0u32, 2].into_array(),
574 buffer![2u32, 1].into_array(),
575 Validity::NonNullable,
576 );
577 assert_arrays_eq!(result, expected, &mut ctx);
578 Ok(())
579 }
580}