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