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