1mod kernel;
5
6use std::fmt::Display;
7use std::fmt::Formatter;
8use std::sync::Arc;
9
10pub use kernel::*;
11use vortex_error::VortexResult;
12use vortex_error::vortex_ensure;
13use vortex_error::vortex_err;
14use vortex_mask::Mask;
15use vortex_mask::MaskValues;
16use vortex_session::VortexSession;
17use vortex_session::registry::CachedId;
18
19use crate::ArrayRef;
20use crate::ExecutionCtx;
21use crate::IntoArray;
22use crate::arrays::BoolArray;
23use crate::arrays::ScalarFnArray;
24use crate::arrays::bool::BoolArrayExt;
25use crate::builders::ArrayBuilder;
26use crate::builders::builder_with_capacity_in;
27use crate::builtins::ArrayBuiltins;
28use crate::dtype::DType;
29use crate::dtype::StructFields;
30use crate::expr::BoundExpression;
31use crate::expr::bound;
32use crate::expr::display::ExprDisplay;
33use crate::scalar_fn::Arity;
34use crate::scalar_fn::ChildName;
35use crate::scalar_fn::EmptyOptions;
36use crate::scalar_fn::ExecutionArgs;
37use crate::scalar_fn::ScalarFnId;
38use crate::scalar_fn::ScalarFnVTable;
39use crate::scalar_fn::ScalarFnVTableExt;
40use crate::scalar_fn::fns::literal::Literal;
41use crate::validity::Validity;
42
43#[derive(Clone)]
51pub struct Zip;
52
53impl Zip {
54 pub fn try_new(
61 if_true: ArrayRef,
62 if_false: ArrayRef,
63 mask: ArrayRef,
64 ) -> VortexResult<ScalarFnArray> {
65 ScalarFnArray::try_new(Zip.bind(EmptyOptions), [if_true, if_false, mask])
66 }
67}
68
69impl ScalarFnVTable for Zip {
70 type Options = EmptyOptions;
71
72 fn id(&self) -> ScalarFnId {
73 static ID: CachedId = CachedId::new("vortex.zip");
74 *ID
75 }
76
77 fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
78 Ok(Some(vec![]))
79 }
80
81 fn deserialize(
82 &self,
83 _metadata: &[u8],
84 _session: &VortexSession,
85 ) -> VortexResult<Self::Options> {
86 Ok(EmptyOptions)
87 }
88
89 fn arity(&self, _options: &Self::Options) -> Arity {
90 Arity::Exact(3)
91 }
92
93 fn child_name(&self, _options: &Self::Options, child_idx: usize) -> ChildName {
94 match child_idx {
95 0 => ChildName::from("if_true"),
96 1 => ChildName::from("if_false"),
97 2 => ChildName::from("mask"),
98 _ => unreachable!("Invalid child index {} for Zip expression", child_idx),
99 }
100 }
101
102 fn fmt_sql(
103 &self,
104 _options: &Self::Options,
105 expr: &dyn ExprDisplay,
106 f: &mut Formatter<'_>,
107 ) -> std::fmt::Result {
108 write!(f, "zip(")?;
109 Display::fmt(expr.display_child(0), f)?;
110 write!(f, ", ")?;
111 Display::fmt(expr.display_child(1), f)?;
112 write!(f, ", ")?;
113 Display::fmt(expr.display_child(2), f)?;
114 write!(f, ")")
115 }
116
117 fn return_dtype(&self, _options: &Self::Options, arg_dtypes: &[DType]) -> VortexResult<DType> {
118 vortex_ensure!(
119 matches!(arg_dtypes[2], DType::Bool(_)),
120 "zip requires mask to be a boolean type, got {}",
121 arg_dtypes[2]
122 );
123 zip_return_dtype(&arg_dtypes[0], &arg_dtypes[1])
124 }
125
126 fn execute(
127 &self,
128 _options: &Self::Options,
129 args: &dyn ExecutionArgs,
130 ctx: &mut ExecutionCtx,
131 ) -> VortexResult<ArrayRef> {
132 let if_true = args.get(0)?;
133 let if_false = args.get(1)?;
134 let mask_array = args.get(2)?;
135
136 let mask = mask_array
137 .execute::<BoolArray>(ctx)?
138 .to_mask_fill_null_false(ctx);
139
140 let return_dtype = zip_return_dtype(if_true.dtype(), if_false.dtype())?;
141
142 if mask.all_true() {
143 return if_true.cast(return_dtype)?.execute(ctx);
144 }
145
146 if mask.all_false() {
147 return if_false.cast(return_dtype)?.execute(ctx);
148 }
149
150 if !if_true.is_canonical() || !if_false.is_canonical() {
151 let if_true = if_true.execute::<ArrayRef>(ctx)?;
152 let if_false = if_false.execute::<ArrayRef>(ctx)?;
153 return mask.into_array().zip(if_true, if_false);
154 }
155
156 zip_impl(&if_true, &if_false, &mask, ctx)
157 }
158
159 fn simplify(
160 &self,
161 _options: &Self::Options,
162 expr: &BoundExpression,
163 ) -> VortexResult<Option<BoundExpression>> {
164 let Some(mask_lit) = expr.child(2).as_opt::<Literal>() else {
165 return Ok(None);
166 };
167
168 if let Some(mask_val) = mask_lit.as_bool().value() {
169 let selected = if mask_val {
170 expr.child(0).clone()
171 } else {
172 expr.child(1).clone()
173 };
174 return Ok(Some(if selected.dtype() == expr.dtype() {
177 selected
178 } else {
179 bound::cast(selected, expr.dtype().clone())
180 }));
181 }
182
183 Ok(None)
184 }
185
186 fn is_strict(&self, _options: &Self::Options) -> bool {
187 false
189 }
190
191 fn is_infallible(&self, _options: &Self::Options) -> bool {
192 true
193 }
194}
195
196pub(crate) fn zip_impl(
197 if_true: &ArrayRef,
198 if_false: &ArrayRef,
199 mask: &Mask,
200 ctx: &mut ExecutionCtx,
201) -> VortexResult<ArrayRef> {
202 assert_eq!(
203 if_true.len(),
204 if_false.len(),
205 "zip requires arrays to have the same size"
206 );
207
208 let return_type = zip_return_dtype(if_true.dtype(), if_false.dtype())?;
209
210 let mask_values = match mask {
211 Mask::AllTrue(_) | Mask::AllFalse(0) => return if_true.cast(return_type),
212 Mask::AllFalse(_) => return if_false.cast(return_type),
213 Mask::Values(values) => values,
214 };
215
216 let if_true = if_true.cast(return_type.clone())?;
219 let if_false = if_false.cast(return_type.clone())?;
220
221 zip_impl_with_builder(
222 &if_true,
223 &if_false,
224 mask_values.as_ref(),
225 builder_with_capacity_in(&return_type, if_true.len(), ctx.allocator()),
226 ctx,
227 )
228}
229
230fn zip_return_dtype(if_true: &DType, if_false: &DType) -> VortexResult<DType> {
231 zip_nullability_union(if_true, if_false).ok_or_else(|| {
232 vortex_err!(
233 "zip requires if_true and if_false to have the same base type, got {} and {}",
234 if_true,
235 if_false
236 )
237 })
238}
239
240fn zip_nullability_union(lhs: &DType, rhs: &DType) -> Option<DType> {
241 let nullability = lhs.nullability() | rhs.nullability();
242
243 match (lhs, rhs) {
244 (DType::List(lhs_element, _), DType::List(rhs_element, _)) => Some(DType::List(
245 Arc::new(zip_nullability_union(lhs_element, rhs_element)?),
246 nullability,
247 )),
248 (
249 DType::FixedSizeList(lhs_element, lhs_size, _),
250 DType::FixedSizeList(rhs_element, rhs_size, _),
251 ) if lhs_size == rhs_size => Some(DType::FixedSizeList(
252 Arc::new(zip_nullability_union(lhs_element, rhs_element)?),
253 *lhs_size,
254 nullability,
255 )),
256 (DType::Map(lhs_map, _), DType::Map(rhs_map, _))
257 if lhs_map.keys_sorted() == rhs_map.keys_sorted() =>
258 {
259 DType::map(
260 zip_nullability_union(&lhs_map.key_dtype(), &rhs_map.key_dtype())?,
261 zip_nullability_union(&lhs_map.value_dtype(), &rhs_map.value_dtype())?,
262 lhs_map.keys_sorted(),
263 nullability,
264 )
265 .ok()
266 }
267 (DType::Struct(lhs_fields, _), DType::Struct(rhs_fields, _))
268 if lhs_fields.names() == rhs_fields.names() =>
269 {
270 let fields = lhs_fields
271 .fields()
272 .zip(rhs_fields.fields())
273 .map(|(lhs, rhs)| zip_nullability_union(&lhs, &rhs))
274 .collect::<Option<Vec<_>>>()?;
275 Some(DType::Struct(
276 StructFields::new(lhs_fields.names().clone(), fields),
277 nullability,
278 ))
279 }
280 (DType::Union(lhs_variants, _), DType::Union(rhs_variants, _))
281 if lhs_variants == rhs_variants =>
282 {
283 Some(DType::Union(lhs_variants.clone(), nullability))
284 }
285 _ if lhs.eq_ignore_nullability(rhs) => Some(lhs.with_nullability(nullability)),
286 _ => None,
287 }
288}
289
290fn zip_impl_with_builder(
291 if_true: &ArrayRef,
292 if_false: &ArrayRef,
293 mask: &MaskValues,
294 mut builder: Box<dyn ArrayBuilder>,
295 ctx: &mut ExecutionCtx,
296) -> VortexResult<ArrayRef> {
297 for (start, end) in mask.slices() {
298 if builder.len() < *start {
299 if_false
300 .slice(builder.len()..*start)?
301 .append_to_builder(builder.as_mut(), ctx)?;
302 }
303 if_true
304 .slice(*start..*end)?
305 .append_to_builder(builder.as_mut(), ctx)?;
306 }
307 if builder.len() < if_false.len() {
308 if_false
309 .slice(builder.len()..if_false.len())?
310 .append_to_builder(builder.as_mut(), ctx)?;
311 }
312 Ok(builder.finish())
313}
314
315pub(crate) fn zip_validity(
324 if_true: Validity,
325 if_false: Validity,
326 mask: &Mask,
327) -> VortexResult<Validity> {
328 match (&if_true, &if_false) {
329 (Validity::NonNullable, Validity::NonNullable) => return Ok(Validity::NonNullable),
330 (Validity::AllValid, Validity::AllValid) => return Ok(Validity::AllValid),
331 (Validity::AllInvalid, Validity::AllInvalid) => return Ok(Validity::AllInvalid),
332 _ => {}
333 }
334
335 let len = mask.len();
336 let validity = mask
337 .clone()
338 .into_array()
339 .zip(if_true.to_array(len), if_false.to_array(len))?;
340 Ok(Validity::Array(validity))
341}
342
343#[cfg(test)]
344mod tests {
345 use vortex_buffer::buffer;
346 use vortex_error::VortexResult;
347 use vortex_mask::Mask;
348
349 use super::zip_impl;
350 use crate::ArrayRef;
351 use crate::IntoArray;
352 use crate::VortexSessionExecute;
353 use crate::array_session;
354 use crate::arrays::ConstantArray;
355 use crate::arrays::PrimitiveArray;
356 use crate::arrays::Struct;
357 use crate::arrays::StructArray;
358 use crate::arrays::VarBinView;
359 use crate::arrays::VarBinViewArray;
360 use crate::assert_arrays_eq;
361 use crate::builders::ArrayBuilder;
362 use crate::builders::BufferGrowthStrategy;
363 use crate::builders::VarBinViewBuilder;
364 use crate::builtins::ArrayBuiltins;
365 use crate::columnar::Columnar;
366 use crate::dtype::DType;
367 use crate::dtype::Nullability;
368 use crate::dtype::PType;
369 use crate::expr::lit;
370 use crate::expr::root;
371 use crate::expr::zip_expr;
372 use crate::scalar::Scalar;
373
374 #[test]
375 fn dtype() {
376 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
377 let expr = zip_expr(lit(true), root(), lit(0i32));
378 let result_dtype = expr.return_dtype(&dtype).unwrap();
379 assert_eq!(
380 result_dtype,
381 DType::Primitive(PType::I32, Nullability::NonNullable)
382 );
383 }
384
385 #[test]
386 fn test_display() {
387 let expr = zip_expr(lit(true), root(), lit(0i32));
388 assert_eq!(expr.to_string(), "zip($, 0i32, true)");
389 }
390
391 #[test]
392 fn test_zip_basic() {
393 let mut ctx = array_session().create_execution_ctx();
394 let mask = Mask::from_iter([true, false, false, true, false]);
395 let if_true = buffer![10, 20, 30, 40, 50].into_array();
396 let if_false = buffer![1, 2, 3, 4, 5].into_array();
397
398 let result = mask.into_array().zip(if_true, if_false).unwrap();
399 let expected = buffer![10, 2, 3, 40, 5].into_array();
400
401 assert_arrays_eq!(result, expected, &mut ctx);
402 }
403
404 #[test]
405 fn test_zip_all_true() {
406 let mut ctx = array_session().create_execution_ctx();
407 let mask = Mask::new_true(4);
408 let if_true = buffer![10, 20, 30, 40].into_array();
409 let if_false =
410 PrimitiveArray::from_option_iter([Some(1), Some(2), Some(3), None]).into_array();
411
412 let result = mask.into_array().zip(if_true, if_false.clone()).unwrap();
413 let expected =
414 PrimitiveArray::from_option_iter([Some(10), Some(20), Some(30), Some(40)]).into_array();
415
416 assert_arrays_eq!(result, expected, &mut ctx);
417 assert_eq!(result.dtype(), if_false.dtype())
418 }
419
420 #[test]
421 fn test_zip_all_false_widens_nullability() {
422 let mut ctx = array_session().create_execution_ctx();
423 let mask = Mask::new_false(4);
424 let if_true =
425 PrimitiveArray::from_option_iter([Some(10), Some(20), Some(30), None]).into_array();
426 let if_false = buffer![1i32, 2, 3, 4].into_array();
427
428 let result = mask.into_array().zip(if_true.clone(), if_false).unwrap();
429 let expected =
430 PrimitiveArray::from_option_iter([Some(1), Some(2), Some(3), Some(4)]).into_array();
431
432 assert_arrays_eq!(result, expected, &mut ctx);
433 assert_eq!(result.dtype(), if_true.dtype());
434 }
435
436 #[test]
437 fn test_zip_impl_all_true_widens_nullability() -> VortexResult<()> {
438 let mask = Mask::new_true(4);
439 let if_true = buffer![10i32, 20, 30, 40].into_array();
440 let if_false =
441 PrimitiveArray::from_option_iter([Some(1), Some(2), Some(3), None]).into_array();
442
443 let mut ctx = array_session().create_execution_ctx();
444 let result = zip_impl(&if_true, &if_false, &mask, &mut ctx)?;
445 assert_arrays_eq!(
446 result,
447 PrimitiveArray::from_option_iter([Some(10i32), Some(20), Some(30), Some(40)])
448 .into_array(),
449 &mut ctx
450 );
451 assert_eq!(result.dtype(), if_false.dtype());
452 Ok(())
453 }
454
455 #[test]
456 fn test_zip_impl_all_false_widens_nullability() -> VortexResult<()> {
457 let mask = Mask::new_false(4);
458 let if_true =
459 PrimitiveArray::from_option_iter([Some(10), Some(20), Some(30), None]).into_array();
460 let if_false = buffer![1i32, 2, 3, 4].into_array();
461
462 let mut ctx = array_session().create_execution_ctx();
463 let result = zip_impl(&if_true, &if_false, &mask, &mut ctx)?;
464 assert_arrays_eq!(
465 result,
466 PrimitiveArray::from_option_iter([Some(1i32), Some(2), Some(3), Some(4)]).into_array(),
467 &mut ctx
468 );
469 assert_eq!(result.dtype(), if_true.dtype());
470 Ok(())
471 }
472
473 #[test]
474 #[should_panic]
475 fn test_invalid_lengths() {
476 let mask = Mask::new_false(4);
477 let if_true = buffer![10, 20, 30].into_array();
478 let if_false = buffer![1, 2, 3, 4].into_array();
479
480 let _result = mask.into_array().zip(if_true, if_false).unwrap();
481 }
482
483 #[test]
484 fn test_fragmentation() -> VortexResult<()> {
485 let len = 100;
486
487 let const1 = ConstantArray::new(
488 Scalar::utf8("hello_this_is_a_longer_string", Nullability::Nullable),
489 len,
490 )
491 .into_array();
492
493 let const2 = ConstantArray::new(
494 Scalar::utf8("world_this_is_another_string", Nullability::Nullable),
495 len,
496 )
497 .into_array();
498
499 let indices: Vec<usize> = (0..len).step_by(2).collect();
500 let mask = Mask::from_indices(len, indices);
501 let mask_array = mask.into_array();
502
503 let mut ctx = array_session().create_execution_ctx();
504 let result = mask_array
505 .zip(const1.clone(), const2.clone())?
506 .execute::<Columnar>(&mut ctx)?
507 .into_array();
508
509 insta::assert_snapshot!(result.display_tree(), @r"
510 root: vortex.varbinview(utf8?, len=100) nbytes=1.66 kB (100.00%) [all_valid]
511 metadata:
512 buffer: buffer_0 host 29 B (align=1) (1.75%)
513 buffer: buffer_1 host 28 B (align=1) (1.69%)
514 buffer: views host 1.60 kB (align=16) (96.56%)
515 ");
516
517 let wrapped1 = StructArray::try_from_iter([("nested", const1)])?.into_array();
518 let wrapped2 = StructArray::try_from_iter([("nested", const2)])?.into_array();
519
520 let wrapped_result = mask_array
521 .zip(wrapped1, wrapped2)?
522 .execute::<ArrayRef>(&mut ctx)?;
523 assert!(wrapped_result.is::<Struct>());
524
525 Ok(())
526 }
527
528 #[test]
529 fn test_varbinview_zip() {
530 let if_true = {
531 let mut builder = VarBinViewBuilder::new_in(
532 DType::Utf8(Nullability::NonNullable),
533 10,
534 Default::default(),
535 BufferGrowthStrategy::fixed(64 * 1024),
536 0.0,
537 vortex_buffer::BufferAllocatorRef::statically_allocated(),
538 );
539 for _ in 0..100 {
540 builder.append_value("Hello");
541 builder.append_value("Hello this is a long string that won't be inlined.");
542 }
543 builder.finish()
544 };
545
546 let if_false = {
547 let mut builder = VarBinViewBuilder::new_in(
548 DType::Utf8(Nullability::NonNullable),
549 10,
550 Default::default(),
551 BufferGrowthStrategy::fixed(64 * 1024),
552 0.0,
553 vortex_buffer::BufferAllocatorRef::statically_allocated(),
554 );
555 for _ in 0..100 {
556 builder.append_value("Hello2");
557 builder.append_value("Hello2 this is a long string that won't be inlined.");
558 }
559 builder.finish()
560 };
561
562 let mask = Mask::from_indices(200, (0..100).filter(|i| i % 3 != 0));
563 let mask_array = mask.clone().into_array();
564
565 let mut ctx = array_session().create_execution_ctx();
566 let zipped = mask_array
567 .zip(if_true, if_false)
568 .unwrap()
569 .execute::<ArrayRef>(&mut ctx)
570 .unwrap();
571 let zipped = zipped.as_opt::<VarBinView>().unwrap();
572 assert_eq!(zipped.data_buffers().len(), 2);
573
574 let true_value = |i: usize| {
575 if i.is_multiple_of(2) {
576 "Hello"
577 } else {
578 "Hello this is a long string that won't be inlined."
579 }
580 };
581 let false_value = |i: usize| {
582 if i.is_multiple_of(2) {
583 "Hello2"
584 } else {
585 "Hello2 this is a long string that won't be inlined."
586 }
587 };
588 let expected = VarBinViewArray::from_iter_str((0..200).map(|i| {
589 if mask.value(i) {
590 true_value(i)
591 } else {
592 false_value(i)
593 }
594 }));
595 assert_arrays_eq!(zipped.array().clone(), expected, &mut ctx);
596 }
597}