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::ListViewArrayExt;
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().null_as_false().execute(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::ListViewArrayExt;
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
334 .into_array()
335 .zip(if_true, if_false)?
336 .execute::<ArrayRef>(&mut ctx)?;
337
338 let expected = list_view(
340 buffer![10i32, 2].into_array(),
341 buffer![0u32, 1, 1].into_array(),
342 buffer![1u32, 0, 1].into_array(),
343 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
344 );
345 assert_arrays_eq!(result, expected, &mut ctx);
346 Ok(())
347 }
348
349 #[test]
352 fn zip_out_of_order_offsets_and_widening() -> VortexResult<()> {
353 let mut ctx = array_session().create_execution_ctx();
354 let if_true = list_view(
356 buffer![7i32, 8, 9, 5, 6].into_array(),
357 buffer![3u32, 0, 1].into_array(),
358 buffer![2u32, 1, 2].into_array(),
359 Validity::NonNullable,
360 );
361 let if_false = list_view(
363 buffer![100i32, 200, 201].into_array(),
364 buffer![0u32, 1, 1].into_array(),
365 buffer![1u32, 0, 2].into_array(),
366 Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
367 );
368 let mask = Mask::from_iter([true, true, false]);
369
370 let result = mask
371 .into_array()
372 .zip(if_true, if_false)?
373 .execute::<ArrayRef>(&mut ctx)?;
374 assert!(result.is::<ListView>());
375
376 let expected = list_view(
378 buffer![5i32, 6, 7, 200, 201].into_array(),
379 buffer![0u32, 2, 3].into_array(),
380 buffer![2u32, 1, 2].into_array(),
381 Validity::AllValid,
382 );
383 assert_arrays_eq!(result, expected, &mut ctx);
384 Ok(())
385 }
386
387 #[test]
390 fn zip_spans_multiple_mask_chunks() -> VortexResult<()> {
391 let mut ctx = array_session().create_execution_ctx();
392 let len = 130usize;
394 let true_elements: Vec<i32> = (0..len as i32).collect();
395 let false_elements: Vec<i32> = (0..len as i32).map(|i| 1000 + i).collect();
396 let offsets: Vec<u64> = (0..len as u64).collect();
397 let sizes: Vec<u64> = vec![1; len];
398
399 let if_true = list_view(
400 true_elements
401 .iter()
402 .copied()
403 .collect::<Buffer<i32>>()
404 .into_array(),
405 offsets
406 .iter()
407 .copied()
408 .collect::<Buffer<u64>>()
409 .into_array(),
410 sizes.iter().copied().collect::<Buffer<u64>>().into_array(),
411 Validity::NonNullable,
412 );
413 let if_false = list_view(
414 false_elements
415 .iter()
416 .copied()
417 .collect::<Buffer<i32>>()
418 .into_array(),
419 offsets
420 .iter()
421 .copied()
422 .collect::<Buffer<u64>>()
423 .into_array(),
424 sizes.iter().copied().collect::<Buffer<u64>>().into_array(),
425 Validity::NonNullable,
426 );
427
428 let mask_bits: Vec<bool> = (0..len).map(|i| i.is_multiple_of(3) || i == 64).collect();
430 let mask = Mask::from_iter(mask_bits.iter().copied());
431
432 let result = mask
433 .into_array()
434 .zip(if_true, if_false)?
435 .execute::<ArrayRef>(&mut ctx)?;
436 assert!(result.is::<ListView>());
437
438 let expected_elements: Vec<i32> = (0..len)
440 .map(|i| {
441 if mask_bits[i] {
442 i as i32
443 } else {
444 1000 + i as i32
445 }
446 })
447 .collect();
448 let expected = list_view(
449 expected_elements
450 .iter()
451 .copied()
452 .collect::<Buffer<i32>>()
453 .into_array(),
454 offsets
455 .iter()
456 .copied()
457 .collect::<Buffer<u64>>()
458 .into_array(),
459 sizes.iter().copied().collect::<Buffer<u64>>().into_array(),
460 Validity::NonNullable,
461 );
462 assert_arrays_eq!(result, expected, &mut ctx);
463 Ok(())
464 }
465
466 #[test]
470 fn zip_handles_offset_mask() -> VortexResult<()> {
471 let mut ctx = array_session().create_execution_ctx();
472 let len = 200usize;
476 let true_elements: Vec<i32> = (0..len as i32).collect();
477 let false_elements: Vec<i32> = (0..len as i32).map(|i| 1000 + i).collect();
478 let offsets: Vec<u64> = (0..len as u64).collect();
479 let sizes: Vec<u64> = vec![1; len];
480
481 let single_element_view = |elements: &[i32]| {
482 list_view(
483 elements
484 .iter()
485 .copied()
486 .collect::<Buffer<i32>>()
487 .into_array(),
488 offsets
489 .iter()
490 .copied()
491 .collect::<Buffer<u64>>()
492 .into_array(),
493 sizes.iter().copied().collect::<Buffer<u64>>().into_array(),
494 Validity::NonNullable,
495 )
496 };
497 let if_true = single_element_view(&true_elements);
498 let if_false = single_element_view(&false_elements);
499
500 let offset = 3usize;
504 let mask_bits: Vec<bool> = (0..offset + len)
505 .map(|i| i.is_multiple_of(3) || i == offset + 64)
506 .collect();
507 let mask = BoolArray::from_iter(mask_bits.iter().copied())
508 .into_array()
509 .slice(offset..offset + len)?;
510
511 let result = mask.zip(if_true, if_false)?.execute::<ArrayRef>(&mut ctx)?;
512 assert!(result.is::<ListView>());
513
514 let expected_elements: Vec<i32> = (0..len)
516 .map(|i| {
517 if mask_bits[offset + i] {
518 i as i32
519 } else {
520 1000 + i as i32
521 }
522 })
523 .collect();
524 let expected = single_element_view(&expected_elements);
525 assert_arrays_eq!(result, expected, &mut ctx);
526 Ok(())
527 }
528
529 #[test]
532 fn zip_flattens_chunked_elements() -> VortexResult<()> {
533 let mut ctx = array_session().create_execution_ctx();
534 let chunked_elements = ChunkedArray::try_new(
536 vec![buffer![1i32, 2].into_array(), buffer![3i32].into_array()],
537 DType::Primitive(PType::I32, Nullability::NonNullable),
538 )?
539 .into_array();
540 let if_true = list_view(
541 chunked_elements,
542 buffer![0u32, 2].into_array(),
543 buffer![2u32, 1].into_array(),
544 Validity::NonNullable,
545 );
546 let if_false = list_view(
548 buffer![10i32, 20].into_array(),
549 buffer![0u32, 1].into_array(),
550 buffer![1u32, 1].into_array(),
551 Validity::NonNullable,
552 );
553 let mask = Mask::from_iter([true, false]);
554
555 let result = mask
556 .into_array()
557 .zip(if_true, if_false)?
558 .execute::<ArrayRef>(&mut ctx)?;
559
560 let result_lv = result
562 .as_opt::<ListView>()
563 .expect("zip keeps the list-view encoding");
564 let chunked = result_lv
565 .elements()
566 .as_opt::<Chunked>()
567 .expect("zip concatenates elements into a chunked array");
568 assert!(
569 chunked.iter_chunks().all(|chunk| !chunk.is::<Chunked>()),
570 "chunked elements must be flattened, not nested",
571 );
572
573 let expected = list_view(
575 buffer![1i32, 2, 20].into_array(),
576 buffer![0u32, 2].into_array(),
577 buffer![2u32, 1].into_array(),
578 Validity::NonNullable,
579 );
580 assert_arrays_eq!(result, expected, &mut ctx);
581 Ok(())
582 }
583}