1use std::fmt;
14use std::fmt::Formatter;
15use std::hash::Hash;
16use std::sync::Arc;
17
18use prost::Message;
19use vortex_error::VortexResult;
20use vortex_error::vortex_bail;
21use vortex_mask::AllOr;
22use vortex_mask::Mask;
23use vortex_session::VortexSession;
24use vortex_session::registry::CachedId;
25
26use crate::ArrayRef;
27use crate::ExecutionCtx;
28use crate::IntoArray;
29use crate::arrays::BoolArray;
30use crate::arrays::ConstantArray;
31use crate::arrays::bool::BoolArrayExt;
32use crate::builders::ArrayBuilder;
33use crate::builders::builder_with_capacity_in;
34use crate::builtins::ArrayBuiltins;
35use crate::dtype::DType;
36use crate::expr::BoundExpression;
37use crate::expr::display::ExprDisplay;
38use crate::proto::expr as pb;
39use crate::scalar::Scalar;
40use crate::scalar_fn::Arity;
41use crate::scalar_fn::ChildName;
42use crate::scalar_fn::EmptyOptions;
43use crate::scalar_fn::ExecutionArgs;
44use crate::scalar_fn::ScalarFnId;
45use crate::scalar_fn::ScalarFnVTable;
46use crate::scalar_fn::ScalarFnVTableExt;
47use crate::scalar_fn::fns::fill_null::FillNull;
48use crate::scalar_fn::fns::is_not_null::IsNotNull;
49use crate::scalar_fn::fns::is_null::IsNull;
50use crate::scalar_fn::fns::literal::Literal;
51use crate::scalar_fn::fns::zip::zip_impl;
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub struct CaseWhenOptions {
56 pub num_when_then_pairs: u32,
58 pub has_else: bool,
61}
62
63impl CaseWhenOptions {
64 pub fn num_children(&self) -> usize {
66 self.num_when_then_pairs as usize * 2 + usize::from(self.has_else)
67 }
68}
69
70impl fmt::Display for CaseWhenOptions {
71 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
72 write!(
73 f,
74 "case_when(pairs={}, else={})",
75 self.num_when_then_pairs, self.has_else
76 )
77 }
78}
79
80#[derive(Clone)]
84pub struct CaseWhen;
85
86impl ScalarFnVTable for CaseWhen {
87 type Options = CaseWhenOptions;
88
89 fn id(&self) -> ScalarFnId {
90 static ID: CachedId = CachedId::new("vortex.case_when");
91 *ID
92 }
93
94 fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
95 vortex_bail!("cannot serialize")
99 }
100
101 fn deserialize(
102 &self,
103 metadata: &[u8],
104 _session: &VortexSession,
105 ) -> VortexResult<Self::Options> {
106 let opts = pb::CaseWhenOpts::decode(metadata)?;
107 if opts.num_children < 2 {
108 vortex_bail!(
109 "CaseWhen expects at least 2 children, got {}",
110 opts.num_children
111 );
112 }
113 Ok(CaseWhenOptions {
114 num_when_then_pairs: opts.num_children / 2,
115 has_else: opts.num_children % 2 == 1,
116 })
117 }
118
119 fn arity(&self, options: &Self::Options) -> Arity {
120 Arity::Exact(options.num_children())
121 }
122
123 fn child_name(&self, options: &Self::Options, child_idx: usize) -> ChildName {
124 let num_pair_children = options.num_when_then_pairs as usize * 2;
125 if child_idx < num_pair_children {
126 let pair_idx = child_idx / 2;
127 if child_idx.is_multiple_of(2) {
128 ChildName::from(Arc::from(format!("when_{pair_idx}")))
129 } else {
130 ChildName::from(Arc::from(format!("then_{pair_idx}")))
131 }
132 } else if options.has_else && child_idx == num_pair_children {
133 ChildName::from("else")
134 } else {
135 unreachable!("Invalid child index {} for CaseWhen", child_idx)
136 }
137 }
138
139 fn fmt_sql(
140 &self,
141 options: &Self::Options,
142 expr: &dyn ExprDisplay,
143 f: &mut Formatter<'_>,
144 ) -> fmt::Result {
145 write!(f, "CASE")?;
146 for i in 0..options.num_when_then_pairs as usize {
147 write!(
148 f,
149 " WHEN {} THEN {}",
150 expr.display_child(i * 2),
151 expr.display_child(i * 2 + 1)
152 )?;
153 }
154 if options.has_else {
155 let else_idx = options.num_when_then_pairs as usize * 2;
156 write!(f, " ELSE {}", expr.display_child(else_idx))?;
157 }
158 write!(f, " END")
159 }
160
161 fn return_dtype(&self, options: &Self::Options, arg_dtypes: &[DType]) -> VortexResult<DType> {
162 if options.num_when_then_pairs == 0 {
163 vortex_bail!("CaseWhen must have at least one WHEN/THEN pair");
164 }
165
166 let expected_len = options.num_children();
167 if arg_dtypes.len() != expected_len {
168 vortex_bail!(
169 "CaseWhen expects {expected_len} argument dtypes, got {}",
170 arg_dtypes.len()
171 );
172 }
173
174 let first_then = &arg_dtypes[1];
178 let mut result_dtype = first_then.clone();
179
180 for i in 1..options.num_when_then_pairs as usize {
181 let then_i = &arg_dtypes[i * 2 + 1];
182 if !first_then.eq_ignore_nullability(then_i) {
183 vortex_bail!(
184 "CaseWhen THEN dtypes must match (ignoring nullability), got {} and {}",
185 first_then,
186 then_i
187 );
188 }
189 result_dtype = result_dtype.union_nullability(then_i.nullability());
190 }
191
192 if options.has_else {
193 let else_dtype = &arg_dtypes[options.num_when_then_pairs as usize * 2];
194 if !result_dtype.eq_ignore_nullability(else_dtype) {
195 vortex_bail!(
196 "CaseWhen THEN and ELSE dtypes must match (ignoring nullability), got {} and {}",
197 first_then,
198 else_dtype
199 );
200 }
201 result_dtype = result_dtype.union_nullability(else_dtype.nullability());
202 } else {
203 result_dtype = result_dtype.as_nullable();
205 }
206
207 Ok(result_dtype)
208 }
209
210 fn execute(
211 &self,
212 options: &Self::Options,
213 args: &dyn ExecutionArgs,
214 ctx: &mut ExecutionCtx,
215 ) -> VortexResult<ArrayRef> {
216 let row_count = args.row_count();
223 let num_pairs = options.num_when_then_pairs as usize;
224
225 let mut remaining = Mask::new_true(row_count);
226 let mut branches: Vec<(Mask, ArrayRef)> = Vec::with_capacity(num_pairs);
227
228 for i in 0..num_pairs {
229 if remaining.all_false() {
230 break;
231 }
232
233 let condition = args.get(i * 2)?;
234 let cond_bool = condition.execute::<BoolArray>(ctx)?;
235 let cond_mask = cond_bool.to_mask_fill_null_false(ctx);
236 let effective_mask = &remaining & &cond_mask;
237
238 if effective_mask.all_false() {
239 continue;
240 }
241
242 let then_value = args.get(i * 2 + 1)?;
243 remaining = remaining.bitand_not(&cond_mask);
244 branches.push((effective_mask, then_value));
245 }
246
247 let else_value: ArrayRef = if options.has_else {
248 args.get(num_pairs * 2)?
249 } else {
250 let then_dtype = args.get(1)?.dtype().as_nullable();
251 ConstantArray::new(Scalar::null(then_dtype), row_count).into_array()
252 };
253
254 if branches.is_empty() {
255 return Ok(else_value);
256 }
257
258 merge_case_branches(branches, else_value, ctx)
259 }
260
261 fn simplify(
262 &self,
263 options: &Self::Options,
264 expr: &BoundExpression,
265 ) -> VortexResult<Option<BoundExpression>> {
266 if options.num_when_then_pairs != 1 || !options.has_else {
276 return Ok(None);
277 }
278
279 let when = expr.child(0);
280 let then = expr.child(1);
281 let els = expr.child(2);
282
283 let (x, fill) = if when.is::<IsNull>() && when.child(0) == els {
285 (els, then)
286 } else if when.is::<IsNotNull>() && when.child(0) == then {
288 (then, els)
289 } else {
290 return Ok(None);
291 };
292
293 let Some(scalar) = fill.as_opt::<Literal>() else {
294 return Ok(None);
295 };
296
297 if scalar.is_null() {
298 return Ok(Some(x.clone()));
300 }
301
302 Ok(Some(FillNull.try_new_bound_expr(
303 EmptyOptions,
304 [x.clone(), fill.clone()],
305 )?))
306 }
307
308 fn is_strict(&self, _options: &Self::Options) -> bool {
309 false
311 }
312
313 fn is_infallible(&self, _options: &Self::Options) -> bool {
314 true
315 }
316}
317
318const SLICE_CROSSOVER_RUN_LEN: usize = 4;
321
322fn merge_case_branches(
326 branches: Vec<(Mask, ArrayRef)>,
327 else_value: ArrayRef,
328 ctx: &mut ExecutionCtx,
329) -> VortexResult<ArrayRef> {
330 if branches.len() == 1 {
331 let (mask, then_value) = &branches[0];
332 return zip_impl(then_value, &else_value, mask, ctx);
333 }
334
335 let output_nullability = branches
336 .iter()
337 .fold(else_value.dtype().nullability(), |acc, (_, arr)| {
338 acc | arr.dtype().nullability()
339 });
340 let output_dtype = else_value.dtype().with_nullability(output_nullability);
341 let branch_arrays: Vec<&ArrayRef> = branches.iter().map(|(_, arr)| arr).collect();
342
343 let mut spans: Vec<(usize, usize, usize)> = Vec::new();
344 for (branch_idx, (mask, _)) in branches.iter().enumerate() {
345 match mask.slices() {
346 AllOr::All => return branch_arrays[branch_idx].cast(output_dtype),
347 AllOr::None => {}
348 AllOr::Some(slices) => {
349 for &(start, end) in slices {
350 spans.push((start, end, branch_idx));
351 }
352 }
353 }
354 }
355 spans.sort_unstable_by_key(|&(start, ..)| start);
356
357 if spans.is_empty() {
358 return else_value.cast(output_dtype);
359 }
360
361 let builder = builder_with_capacity_in(&output_dtype, else_value.len(), ctx.allocator());
362
363 let fragmented = spans.len() > else_value.len() / SLICE_CROSSOVER_RUN_LEN;
364 if fragmented {
365 merge_row_by_row(
366 &branch_arrays,
367 &else_value,
368 &spans,
369 &output_dtype,
370 builder,
371 ctx,
372 )
373 } else {
374 merge_run_by_run(
375 &branch_arrays,
376 &else_value,
377 &spans,
378 &output_dtype,
379 builder,
380 ctx,
381 )
382 }
383}
384
385fn merge_row_by_row(
388 branch_arrays: &[&ArrayRef],
389 else_value: &ArrayRef,
390 spans: &[(usize, usize, usize)],
391 output_dtype: &DType,
392 mut builder: Box<dyn ArrayBuilder>,
393 ctx: &mut ExecutionCtx,
394) -> VortexResult<ArrayRef> {
395 let mut pos = 0;
396 for &(start, end, branch_idx) in spans {
397 for row in pos..start {
398 let scalar = else_value.execute_scalar(row, ctx)?;
399 builder.append_scalar(&scalar.cast(output_dtype)?)?;
400 }
401 for row in start..end {
402 let scalar = branch_arrays[branch_idx].execute_scalar(row, ctx)?;
403 builder.append_scalar(&scalar.cast(output_dtype)?)?;
404 }
405 pos = end;
406 }
407 for row in pos..else_value.len() {
408 let scalar = else_value.execute_scalar(row, ctx)?;
409 builder.append_scalar(&scalar.cast(output_dtype)?)?;
410 }
411
412 Ok(builder.finish())
413}
414
415fn merge_run_by_run(
419 branch_arrays: &[&ArrayRef],
420 else_value: &ArrayRef,
421 spans: &[(usize, usize, usize)],
422 output_dtype: &DType,
423 mut builder: Box<dyn ArrayBuilder>,
424 ctx: &mut ExecutionCtx,
425) -> VortexResult<ArrayRef> {
426 let else_value = else_value.cast(output_dtype.clone())?;
427 let len = else_value.len();
428 for (start, end, branch_idx) in spans {
429 if builder.len() < *start {
430 else_value
431 .slice(builder.len()..*start)?
432 .append_to_builder(builder.as_mut(), ctx)?;
433 }
434 branch_arrays[*branch_idx]
435 .cast(output_dtype.clone())?
436 .slice(*start..*end)?
437 .append_to_builder(builder.as_mut(), ctx)?;
438 }
439 if builder.len() < len {
440 else_value
441 .slice(builder.len()..len)?
442 .append_to_builder(builder.as_mut(), ctx)?;
443 }
444
445 Ok(builder.finish())
446}
447
448#[cfg(test)]
449mod tests {
450 use std::sync::LazyLock;
451
452 use vortex_buffer::buffer;
453 use vortex_error::VortexExpect as _;
454 use vortex_session::VortexSession;
455
456 use super::*;
457 use crate::Canonical;
458 use crate::IntoArray;
459 use crate::VortexSessionExecute;
460 use crate::arrays::BoolArray;
461 use crate::arrays::PrimitiveArray;
462 use crate::arrays::StructArray;
463 use crate::assert_arrays_eq;
464 use crate::dtype::DType;
465 use crate::dtype::Nullability;
466 use crate::dtype::PType;
467 use crate::dtype::StructFields;
468 use crate::expr::Expression;
469 use crate::expr::case_when;
470 use crate::expr::case_when_no_else;
471 use crate::expr::col;
472 use crate::expr::eq;
473 use crate::expr::get_item;
474 use crate::expr::gt;
475 use crate::expr::is_not_null;
476 use crate::expr::is_null;
477 use crate::expr::lit;
478 use crate::expr::nested_case_when;
479 use crate::expr::root;
480 use crate::expr::test_harness;
481 use crate::scalar::Scalar;
482
483 static SESSION: LazyLock<VortexSession> = LazyLock::new(crate::array_session);
484
485 fn evaluate_expr(expr: &Expression, array: &ArrayRef) -> ArrayRef {
487 let mut ctx = SESSION.create_execution_ctx();
488 array
489 .clone()
490 .apply(expr)
491 .unwrap()
492 .execute::<Canonical>(&mut ctx)
493 .unwrap()
494 .into_array()
495 }
496
497 fn evaluate_bound_expr(expr: &BoundExpression, array: &ArrayRef) -> ArrayRef {
498 let mut ctx = SESSION.create_execution_ctx();
499 array
500 .clone()
501 .apply_bound(expr)
502 .unwrap()
503 .execute::<Canonical>(&mut ctx)
504 .unwrap()
505 .into_array()
506 }
507
508 #[test]
511 #[should_panic(expected = "cannot serialize")]
512 fn test_serialization_roundtrip() {
513 let options = CaseWhenOptions {
514 num_when_then_pairs: 1,
515 has_else: true,
516 };
517 let serialized = CaseWhen.serialize(&options).unwrap().unwrap();
518 let deserialized = CaseWhen
519 .deserialize(&serialized, &VortexSession::empty())
520 .unwrap();
521 assert_eq!(options, deserialized);
522 }
523
524 #[test]
525 #[should_panic(expected = "cannot serialize")]
526 fn test_serialization_no_else() {
527 let options = CaseWhenOptions {
528 num_when_then_pairs: 1,
529 has_else: false,
530 };
531 let serialized = CaseWhen.serialize(&options).unwrap().unwrap();
532 let deserialized = CaseWhen
533 .deserialize(&serialized, &VortexSession::empty())
534 .unwrap();
535 assert_eq!(options, deserialized);
536 }
537
538 #[test]
541 fn test_display_with_else() {
542 let expr = case_when(gt(col("value"), lit(0i32)), lit(100i32), lit(0i32));
543 let display = format!("{}", expr);
544 assert!(display.contains("CASE"));
545 assert!(display.contains("WHEN"));
546 assert!(display.contains("THEN"));
547 assert!(display.contains("ELSE"));
548 assert!(display.contains("END"));
549 }
550
551 #[test]
552 fn test_display_no_else() {
553 let expr = case_when_no_else(gt(col("value"), lit(0i32)), lit(100i32));
554 let display = format!("{}", expr);
555 assert!(display.contains("CASE"));
556 assert!(display.contains("WHEN"));
557 assert!(display.contains("THEN"));
558 assert!(!display.contains("ELSE"));
559 assert!(display.contains("END"));
560 }
561
562 #[test]
563 fn test_display_nested_nary() {
564 let expr = nested_case_when(
566 vec![
567 (gt(col("x"), lit(10i32)), lit("high")),
568 (gt(col("x"), lit(5i32)), lit("medium")),
569 ],
570 Some(lit("low")),
571 );
572 let display = format!("{}", expr);
573 assert_eq!(display.matches("CASE").count(), 1);
574 assert_eq!(display.matches("WHEN").count(), 2);
575 assert_eq!(display.matches("THEN").count(), 2);
576 }
577
578 #[test]
581 fn test_return_dtype_with_else() {
582 let expr = case_when(lit(true), lit(100i32), lit(0i32));
583 let input_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
584 let result_dtype = expr.return_dtype(&input_dtype).unwrap();
585 assert_eq!(
586 result_dtype,
587 DType::Primitive(PType::I32, Nullability::NonNullable)
588 );
589 }
590
591 #[test]
592 fn test_return_dtype_with_nullable_else() {
593 let expr = case_when(
594 lit(true),
595 lit(100i32),
596 lit(Scalar::null(DType::Primitive(
597 PType::I32,
598 Nullability::Nullable,
599 ))),
600 );
601 let input_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
602 let result_dtype = expr.return_dtype(&input_dtype).unwrap();
603 assert_eq!(
604 result_dtype,
605 DType::Primitive(PType::I32, Nullability::Nullable)
606 );
607 }
608
609 #[test]
610 fn test_return_dtype_without_else_is_nullable() {
611 let expr = case_when_no_else(lit(true), lit(100i32));
612 let input_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
613 let result_dtype = expr.return_dtype(&input_dtype).unwrap();
614 assert_eq!(
615 result_dtype,
616 DType::Primitive(PType::I32, Nullability::Nullable)
617 );
618 }
619
620 #[test]
621 fn test_return_dtype_with_struct_input() {
622 let dtype = test_harness::struct_dtype();
623 let expr = case_when(
624 gt(get_item("col1", root()), lit(10u16)),
625 lit(100i32),
626 lit(0i32),
627 );
628 let result_dtype = expr.return_dtype(&dtype).unwrap();
629 assert_eq!(
630 result_dtype,
631 DType::Primitive(PType::I32, Nullability::NonNullable)
632 );
633 }
634
635 #[test]
636 fn test_return_dtype_mismatched_then_else_errors() {
637 let expr = case_when(lit(true), lit(100i32), lit("zero"));
638 let input_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
639 let err = expr.return_dtype(&input_dtype).unwrap_err();
640 assert!(
641 err.to_string()
642 .contains("THEN and ELSE dtypes must match (ignoring nullability)")
643 );
644 }
645
646 #[test]
649 fn test_arity_with_else() {
650 let options = CaseWhenOptions {
651 num_when_then_pairs: 1,
652 has_else: true,
653 };
654 assert_eq!(CaseWhen.arity(&options), Arity::Exact(3));
655 }
656
657 #[test]
658 fn test_arity_without_else() {
659 let options = CaseWhenOptions {
660 num_when_then_pairs: 1,
661 has_else: false,
662 };
663 assert_eq!(CaseWhen.arity(&options), Arity::Exact(2));
664 }
665
666 #[test]
669 fn test_child_names() {
670 let options = CaseWhenOptions {
671 num_when_then_pairs: 1,
672 has_else: true,
673 };
674 assert_eq!(CaseWhen.child_name(&options, 0).to_string(), "when_0");
675 assert_eq!(CaseWhen.child_name(&options, 1).to_string(), "then_0");
676 assert_eq!(CaseWhen.child_name(&options, 2).to_string(), "else");
677 }
678
679 #[test]
682 #[should_panic(expected = "cannot serialize")]
683 fn test_serialization_roundtrip_nary() {
684 let options = CaseWhenOptions {
685 num_when_then_pairs: 3,
686 has_else: true,
687 };
688 let serialized = CaseWhen.serialize(&options).unwrap().unwrap();
689 let deserialized = CaseWhen
690 .deserialize(&serialized, &VortexSession::empty())
691 .unwrap();
692 assert_eq!(options, deserialized);
693 }
694
695 #[test]
696 #[should_panic(expected = "cannot serialize")]
697 fn test_serialization_roundtrip_nary_no_else() {
698 let options = CaseWhenOptions {
699 num_when_then_pairs: 4,
700 has_else: false,
701 };
702 let serialized = CaseWhen.serialize(&options).unwrap().unwrap();
703 let deserialized = CaseWhen
704 .deserialize(&serialized, &VortexSession::empty())
705 .unwrap();
706 assert_eq!(options, deserialized);
707 }
708
709 #[test]
712 fn test_arity_nary_with_else() {
713 let options = CaseWhenOptions {
714 num_when_then_pairs: 3,
715 has_else: true,
716 };
717 assert_eq!(CaseWhen.arity(&options), Arity::Exact(7));
719 }
720
721 #[test]
722 fn test_arity_nary_without_else() {
723 let options = CaseWhenOptions {
724 num_when_then_pairs: 3,
725 has_else: false,
726 };
727 assert_eq!(CaseWhen.arity(&options), Arity::Exact(6));
729 }
730
731 #[test]
734 fn test_child_names_nary() {
735 let options = CaseWhenOptions {
736 num_when_then_pairs: 3,
737 has_else: true,
738 };
739 assert_eq!(CaseWhen.child_name(&options, 0).to_string(), "when_0");
740 assert_eq!(CaseWhen.child_name(&options, 1).to_string(), "then_0");
741 assert_eq!(CaseWhen.child_name(&options, 2).to_string(), "when_1");
742 assert_eq!(CaseWhen.child_name(&options, 3).to_string(), "then_1");
743 assert_eq!(CaseWhen.child_name(&options, 4).to_string(), "when_2");
744 assert_eq!(CaseWhen.child_name(&options, 5).to_string(), "then_2");
745 assert_eq!(CaseWhen.child_name(&options, 6).to_string(), "else");
746 }
747
748 #[test]
751 fn test_return_dtype_nary_mismatched_then_types_errors() {
752 let expr = nested_case_when(
753 vec![(lit(true), lit(100i32)), (lit(false), lit("oops"))],
754 Some(lit(0i32)),
755 );
756 let input_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
757 let err = expr.return_dtype(&input_dtype).unwrap_err();
758 assert!(err.to_string().contains("THEN dtypes must match"));
759 }
760
761 #[test]
762 fn test_return_dtype_nary_mixed_nullability() {
763 let non_null_then = lit(100i32);
766 let nullable_then = lit(Scalar::null(DType::Primitive(
767 PType::I32,
768 Nullability::Nullable,
769 )));
770 let expr = nested_case_when(
771 vec![(lit(true), non_null_then), (lit(false), nullable_then)],
772 Some(lit(0i32)),
773 );
774 let input_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
775 let result = expr.return_dtype(&input_dtype).unwrap();
776 assert_eq!(result, DType::Primitive(PType::I32, Nullability::Nullable));
777 }
778
779 #[test]
780 fn test_return_dtype_nary_no_else_is_nullable() {
781 let expr = nested_case_when(
782 vec![(lit(true), lit(10i32)), (lit(false), lit(20i32))],
783 None,
784 );
785 let input_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
786 let result = expr.return_dtype(&input_dtype).unwrap();
787 assert_eq!(result, DType::Primitive(PType::I32, Nullability::Nullable));
788 }
789
790 #[test]
793 fn test_replace_children() {
794 let expr = case_when(lit(true), lit(1i32), lit(0i32));
795 expr.with_children([lit(false), lit(2i32), lit(3i32)])
796 .vortex_expect("operation should succeed in test");
797 }
798
799 #[test]
802 fn test_evaluate_simple_condition() {
803 let mut ctx = SESSION.create_execution_ctx();
804 let test_array =
805 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
806 .unwrap()
807 .into_array();
808
809 let expr = case_when(
810 gt(get_item("value", root()), lit(2i32)),
811 lit(100i32),
812 lit(0i32),
813 );
814
815 let result = evaluate_expr(&expr, &test_array);
816 assert_arrays_eq!(
817 result,
818 buffer![0i32, 0, 100, 100, 100].into_array(),
819 &mut ctx
820 );
821 }
822
823 #[test]
824 fn test_evaluate_nary_multiple_conditions() {
825 let mut ctx = SESSION.create_execution_ctx();
826 let test_array =
828 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
829 .unwrap()
830 .into_array();
831
832 let expr = nested_case_when(
833 vec![
834 (eq(get_item("value", root()), lit(1i32)), lit(10i32)),
835 (eq(get_item("value", root()), lit(3i32)), lit(30i32)),
836 ],
837 Some(lit(0i32)),
838 );
839
840 let result = evaluate_expr(&expr, &test_array);
841 assert_arrays_eq!(result, buffer![10i32, 0, 30, 0, 0].into_array(), &mut ctx);
842 }
843
844 #[test]
845 fn test_evaluate_nary_first_match_wins() {
846 let mut ctx = SESSION.create_execution_ctx();
847 let test_array =
848 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
849 .unwrap()
850 .into_array();
851
852 let expr = nested_case_when(
854 vec![
855 (gt(get_item("value", root()), lit(2i32)), lit(100i32)),
856 (gt(get_item("value", root()), lit(3i32)), lit(200i32)),
857 ],
858 Some(lit(0i32)),
859 );
860
861 let result = evaluate_expr(&expr, &test_array);
862 assert_arrays_eq!(
863 result,
864 buffer![0i32, 0, 100, 100, 100].into_array(),
865 &mut ctx
866 );
867 }
868
869 #[test]
870 fn test_evaluate_no_else_returns_null() {
871 let mut ctx = SESSION.create_execution_ctx();
872 let test_array =
873 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
874 .unwrap()
875 .into_array();
876
877 let expr = case_when_no_else(gt(get_item("value", root()), lit(3i32)), lit(100i32));
878
879 let result = evaluate_expr(&expr, &test_array);
880 assert!(result.dtype().is_nullable());
881 assert_arrays_eq!(
882 result,
883 PrimitiveArray::from_option_iter([None::<i32>, None, None, Some(100), Some(100)])
884 .into_array(),
885 &mut ctx
886 );
887 }
888
889 #[test]
890 fn test_evaluate_all_conditions_false() {
891 let mut ctx = SESSION.create_execution_ctx();
892 let test_array =
893 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
894 .unwrap()
895 .into_array();
896
897 let expr = case_when(
898 gt(get_item("value", root()), lit(100i32)),
899 lit(1i32),
900 lit(0i32),
901 );
902
903 let result = evaluate_expr(&expr, &test_array);
904 assert_arrays_eq!(result, buffer![0i32, 0, 0, 0, 0].into_array(), &mut ctx);
905 }
906
907 #[test]
908 fn test_evaluate_all_conditions_true() {
909 let mut ctx = SESSION.create_execution_ctx();
910 let test_array =
911 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
912 .unwrap()
913 .into_array();
914
915 let expr = case_when(
916 gt(get_item("value", root()), lit(0i32)),
917 lit(100i32),
918 lit(0i32),
919 );
920
921 let result = evaluate_expr(&expr, &test_array);
922 assert_arrays_eq!(
923 result,
924 buffer![100i32, 100, 100, 100, 100].into_array(),
925 &mut ctx
926 );
927 }
928
929 #[test]
930 fn test_evaluate_all_true_no_else_returns_correct_dtype() {
931 let mut ctx = SESSION.create_execution_ctx();
932 let test_array = StructArray::from_fields(&[("value", buffer![1i32, 2, 3].into_array())])
935 .unwrap()
936 .into_array();
937
938 let expr = case_when_no_else(gt(get_item("value", root()), lit(0i32)), lit(100i32));
939
940 let result = evaluate_expr(&expr, &test_array);
941 assert!(
942 result.dtype().is_nullable(),
943 "result dtype must be Nullable, got {:?}",
944 result.dtype()
945 );
946 assert_arrays_eq!(
947 result,
948 PrimitiveArray::from_option_iter([Some(100i32), Some(100), Some(100)]).into_array(),
949 &mut ctx
950 );
951 }
952
953 #[test]
954 fn test_merge_case_branches_widens_nullability_of_later_branch() -> VortexResult<()> {
955 let mut ctx = SESSION.create_execution_ctx();
956 let test_array =
964 StructArray::from_fields(&[("value", buffer![0i32, 1, 2].into_array())])?.into_array();
965
966 let nullable_20 =
967 Scalar::from(20i32).cast(&DType::Primitive(PType::I32, Nullability::Nullable))?;
968
969 let expr = nested_case_when(
970 vec![
971 (eq(get_item("value", root()), lit(0i32)), lit(10i32)),
972 (eq(get_item("value", root()), lit(1i32)), lit(nullable_20)),
973 ],
974 Some(lit(0i32)),
975 );
976
977 let result = evaluate_expr(&expr, &test_array);
978 assert!(
979 result.dtype().is_nullable(),
980 "result dtype must be Nullable, got {:?}",
981 result.dtype()
982 );
983 assert_arrays_eq!(
984 result,
985 PrimitiveArray::from_option_iter([Some(10), Some(20), Some(0)]).into_array(),
986 &mut ctx
987 );
988 Ok(())
989 }
990
991 #[test]
992 fn test_evaluate_with_literal_condition() {
993 let mut ctx = SESSION.create_execution_ctx();
994 let test_array = buffer![1i32, 2, 3].into_array();
995 let expr = case_when(lit(true), lit(100i32), lit(0i32));
996 let result = evaluate_expr(&expr, &test_array);
997
998 assert_arrays_eq!(result, buffer![100i32, 100, 100].into_array(), &mut ctx);
999 }
1000
1001 #[test]
1002 fn test_evaluate_with_bool_column_result() {
1003 let mut ctx = SESSION.create_execution_ctx();
1004 let test_array =
1005 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
1006 .unwrap()
1007 .into_array();
1008
1009 let expr = case_when(
1010 gt(get_item("value", root()), lit(2i32)),
1011 lit(true),
1012 lit(false),
1013 );
1014
1015 let result = evaluate_expr(&expr, &test_array);
1016 assert_arrays_eq!(
1017 result,
1018 BoolArray::from_iter([false, false, true, true, true]).into_array(),
1019 &mut ctx
1020 );
1021 }
1022
1023 #[test]
1024 fn test_evaluate_with_nullable_condition() {
1025 let mut ctx = SESSION.create_execution_ctx();
1026 let test_array = StructArray::from_fields(&[(
1027 "cond",
1028 BoolArray::from_iter([Some(true), None, Some(false), None, Some(true)]).into_array(),
1029 )])
1030 .unwrap()
1031 .into_array();
1032
1033 let expr = case_when(get_item("cond", root()), lit(100i32), lit(0i32));
1034
1035 let result = evaluate_expr(&expr, &test_array);
1036 assert_arrays_eq!(result, buffer![100i32, 0, 0, 0, 100].into_array(), &mut ctx);
1037 }
1038
1039 #[test]
1040 fn test_evaluate_with_nullable_result_values() {
1041 let mut ctx = SESSION.create_execution_ctx();
1042 let test_array = StructArray::from_fields(&[
1043 ("value", buffer![1i32, 2, 3, 4, 5].into_array()),
1044 (
1045 "result",
1046 PrimitiveArray::from_option_iter([Some(10), None, Some(30), Some(40), Some(50)])
1047 .into_array(),
1048 ),
1049 ])
1050 .unwrap()
1051 .into_array();
1052
1053 let expr = case_when(
1054 gt(get_item("value", root()), lit(2i32)),
1055 get_item("result", root()),
1056 lit(0i32),
1057 );
1058
1059 let result = evaluate_expr(&expr, &test_array);
1060 assert_arrays_eq!(
1061 result,
1062 PrimitiveArray::from_option_iter([Some(0i32), Some(0), Some(30), Some(40), Some(50)])
1063 .into_array(),
1064 &mut ctx
1065 );
1066 }
1067
1068 #[test]
1069 fn test_evaluate_with_all_null_condition() {
1070 let mut ctx = SESSION.create_execution_ctx();
1071 let test_array = StructArray::from_fields(&[(
1072 "cond",
1073 BoolArray::from_iter([None, None, None]).into_array(),
1074 )])
1075 .unwrap()
1076 .into_array();
1077
1078 let expr = case_when(get_item("cond", root()), lit(100i32), lit(0i32));
1079
1080 let result = evaluate_expr(&expr, &test_array);
1081 assert_arrays_eq!(result, buffer![0i32, 0, 0].into_array(), &mut ctx);
1082 }
1083
1084 #[test]
1087 fn test_evaluate_nary_no_else_returns_null() {
1088 let mut ctx = SESSION.create_execution_ctx();
1089 let test_array =
1090 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
1091 .unwrap()
1092 .into_array();
1093
1094 let expr = nested_case_when(
1096 vec![
1097 (eq(get_item("value", root()), lit(1i32)), lit(10i32)),
1098 (eq(get_item("value", root()), lit(3i32)), lit(30i32)),
1099 ],
1100 None,
1101 );
1102
1103 let result = evaluate_expr(&expr, &test_array);
1104 assert!(result.dtype().is_nullable());
1105 assert_arrays_eq!(
1106 result,
1107 PrimitiveArray::from_option_iter([Some(10i32), None, Some(30), None, None])
1108 .into_array(),
1109 &mut ctx
1110 );
1111 }
1112
1113 #[test]
1114 fn test_evaluate_nary_many_conditions() {
1115 let mut ctx = SESSION.create_execution_ctx();
1116 let test_array =
1117 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4, 5].into_array())])
1118 .unwrap()
1119 .into_array();
1120
1121 let expr = nested_case_when(
1123 vec![
1124 (eq(get_item("value", root()), lit(1i32)), lit(10i32)),
1125 (eq(get_item("value", root()), lit(2i32)), lit(20i32)),
1126 (eq(get_item("value", root()), lit(3i32)), lit(30i32)),
1127 (eq(get_item("value", root()), lit(4i32)), lit(40i32)),
1128 (eq(get_item("value", root()), lit(5i32)), lit(50i32)),
1129 ],
1130 Some(lit(0i32)),
1131 );
1132
1133 let result = evaluate_expr(&expr, &test_array);
1134 assert_arrays_eq!(
1135 result,
1136 buffer![10i32, 20, 30, 40, 50].into_array(),
1137 &mut ctx
1138 );
1139 }
1140
1141 #[test]
1142 fn test_evaluate_nary_all_false_no_else() {
1143 let mut ctx = SESSION.create_execution_ctx();
1144 let test_array = StructArray::from_fields(&[("value", buffer![1i32, 2, 3].into_array())])
1145 .unwrap()
1146 .into_array();
1147
1148 let expr = nested_case_when(
1150 vec![
1151 (gt(get_item("value", root()), lit(100i32)), lit(10i32)),
1152 (gt(get_item("value", root()), lit(200i32)), lit(20i32)),
1153 ],
1154 None,
1155 );
1156
1157 let result = evaluate_expr(&expr, &test_array);
1158 assert!(result.dtype().is_nullable());
1159 assert_arrays_eq!(
1160 result,
1161 PrimitiveArray::from_option_iter([None::<i32>, None, None]).into_array(),
1162 &mut ctx
1163 );
1164 }
1165
1166 #[test]
1167 fn test_evaluate_nary_overlapping_conditions_first_wins() {
1168 let mut ctx = SESSION.create_execution_ctx();
1169 let test_array =
1170 StructArray::from_fields(&[("value", buffer![10i32, 20, 30].into_array())])
1171 .unwrap()
1172 .into_array();
1173
1174 let expr = nested_case_when(
1178 vec![
1179 (gt(get_item("value", root()), lit(5i32)), lit(1i32)),
1180 (gt(get_item("value", root()), lit(0i32)), lit(2i32)),
1181 (gt(get_item("value", root()), lit(15i32)), lit(3i32)),
1182 ],
1183 Some(lit(0i32)),
1184 );
1185
1186 let result = evaluate_expr(&expr, &test_array);
1187 assert_arrays_eq!(result, buffer![1i32, 1, 1].into_array(), &mut ctx);
1189 }
1190
1191 #[test]
1192 fn test_evaluate_nary_early_exit_when_remaining_empty() {
1193 let mut ctx = SESSION.create_execution_ctx();
1194 let test_array = StructArray::from_fields(&[("value", buffer![1i32, 2, 3].into_array())])
1197 .unwrap()
1198 .into_array();
1199
1200 let expr = nested_case_when(
1201 vec![
1202 (gt(get_item("value", root()), lit(0i32)), lit(100i32)),
1203 (gt(get_item("value", root()), lit(0i32)), lit(999i32)),
1205 ],
1206 Some(lit(0i32)),
1207 );
1208
1209 let result = evaluate_expr(&expr, &test_array);
1210 assert_arrays_eq!(result, buffer![100i32, 100, 100].into_array(), &mut ctx);
1211 }
1212
1213 #[test]
1214 fn test_evaluate_nary_skips_branch_with_empty_effective_mask() {
1215 let mut ctx = SESSION.create_execution_ctx();
1216 let test_array = StructArray::from_fields(&[("value", buffer![1i32, 2, 3].into_array())])
1219 .unwrap()
1220 .into_array();
1221
1222 let expr = nested_case_when(
1223 vec![
1224 (eq(get_item("value", root()), lit(1i32)), lit(10i32)),
1225 (eq(get_item("value", root()), lit(1i32)), lit(999i32)),
1228 (eq(get_item("value", root()), lit(2i32)), lit(20i32)),
1229 ],
1230 Some(lit(0i32)),
1231 );
1232
1233 let result = evaluate_expr(&expr, &test_array);
1234 assert_arrays_eq!(result, buffer![10i32, 20, 0].into_array(), &mut ctx);
1235 }
1236
1237 #[test]
1238 fn test_evaluate_nary_string_output() -> VortexResult<()> {
1239 let test_array =
1241 StructArray::from_fields(&[("value", buffer![1i32, 2, 3, 4].into_array())])?
1242 .into_array();
1243
1244 let expr = nested_case_when(
1248 vec![
1249 (gt(get_item("value", root()), lit(2i32)), lit("high")),
1250 (gt(get_item("value", root()), lit(0i32)), lit("low")),
1251 ],
1252 Some(lit("none")),
1253 );
1254
1255 let result = evaluate_expr(&expr, &test_array);
1256 assert_eq!(
1257 result.execute_scalar(0, &mut SESSION.create_execution_ctx())?,
1258 Scalar::utf8("low", Nullability::NonNullable)
1259 );
1260 assert_eq!(
1261 result.execute_scalar(1, &mut SESSION.create_execution_ctx())?,
1262 Scalar::utf8("low", Nullability::NonNullable)
1263 );
1264 assert_eq!(
1265 result.execute_scalar(2, &mut SESSION.create_execution_ctx())?,
1266 Scalar::utf8("high", Nullability::NonNullable)
1267 );
1268 assert_eq!(
1269 result.execute_scalar(3, &mut SESSION.create_execution_ctx())?,
1270 Scalar::utf8("high", Nullability::NonNullable)
1271 );
1272 Ok(())
1273 }
1274
1275 #[test]
1276 fn test_evaluate_nary_with_nullable_conditions() {
1277 let mut ctx = SESSION.create_execution_ctx();
1278 let test_array = StructArray::from_fields(&[
1279 (
1280 "cond1",
1281 BoolArray::from_iter([Some(true), None, Some(false)]).into_array(),
1282 ),
1283 (
1284 "cond2",
1285 BoolArray::from_iter([Some(false), Some(true), None]).into_array(),
1286 ),
1287 ])
1288 .unwrap()
1289 .into_array();
1290
1291 let expr = nested_case_when(
1292 vec![
1293 (get_item("cond1", root()), lit(10i32)),
1294 (get_item("cond2", root()), lit(20i32)),
1295 ],
1296 Some(lit(0i32)),
1297 );
1298
1299 let result = evaluate_expr(&expr, &test_array);
1300 assert_arrays_eq!(result, buffer![10i32, 20, 0].into_array(), &mut ctx);
1304 }
1305
1306 fn nullable_i64_scope(fields: &[&str]) -> DType {
1310 DType::Struct(
1311 StructFields::new(
1312 fields.to_vec().into(),
1313 vec![DType::Primitive(PType::I64, Nullability::Nullable); fields.len()],
1314 ),
1315 Nullability::NonNullable,
1316 )
1317 }
1318
1319 #[test]
1320 fn test_simplify_coalesce_is_null_rewrites_to_fill_null() -> VortexResult<()> {
1321 let expr = case_when(is_null(col("x")), lit(0i64), col("x"));
1323 let optimized = expr
1324 .bind(&nullable_i64_scope(&["x"]))?
1325 .optimize_recursive()?;
1326 assert!(
1327 optimized.to_string().starts_with("vortex.fill_null"),
1328 "expected fill_null, got {optimized}"
1329 );
1330 Ok(())
1331 }
1332
1333 #[test]
1334 fn test_simplify_coalesce_is_not_null_rewrites_to_fill_null() -> VortexResult<()> {
1335 let expr = case_when(is_not_null(col("x")), col("x"), lit(0i64));
1337 let optimized = expr
1338 .bind(&nullable_i64_scope(&["x"]))?
1339 .optimize_recursive()?;
1340 assert!(
1341 optimized.to_string().starts_with("vortex.fill_null"),
1342 "expected fill_null, got {optimized}"
1343 );
1344 Ok(())
1345 }
1346
1347 #[test]
1348 fn test_simplify_does_not_fire_when_operands_differ() -> VortexResult<()> {
1349 let expr = case_when(is_null(col("x")), lit(0i64), col("y"));
1351 let optimized = expr
1352 .bind(&nullable_i64_scope(&["x", "y"]))?
1353 .optimize_recursive()?;
1354 let s = optimized.to_string();
1355 assert!(s.contains("CASE"), "expected CASE WHEN to remain, got {s}");
1356 assert!(!s.contains("fill_null"), "must not rewrite, got {s}");
1357 Ok(())
1358 }
1359
1360 #[test]
1361 fn test_simplify_does_not_fire_for_non_constant_fill() -> VortexResult<()> {
1362 let expr = case_when(is_null(col("x")), col("c"), col("x"));
1365 let optimized = expr
1366 .bind(&nullable_i64_scope(&["x", "c"]))?
1367 .optimize_recursive()?;
1368 let s = optimized.to_string();
1369 assert!(s.contains("CASE"), "expected CASE WHEN to remain, got {s}");
1370 assert!(!s.contains("fill_null"), "must not rewrite, got {s}");
1371 Ok(())
1372 }
1373
1374 #[test]
1375 fn test_simplify_null_fill_collapses_to_input() -> VortexResult<()> {
1376 let null_fill = || {
1380 lit(Scalar::null(DType::Primitive(
1381 PType::I64,
1382 Nullability::Nullable,
1383 )))
1384 };
1385
1386 for expr in [
1387 case_when(is_null(col("x")), null_fill(), col("x")),
1388 case_when(is_not_null(col("x")), col("x"), null_fill()),
1389 ] {
1390 let optimized = expr
1391 .bind(&nullable_i64_scope(&["x"]))?
1392 .optimize_recursive()?;
1393 assert_eq!(
1394 optimized.to_string(),
1395 "$.x",
1396 "expected collapse to input column, got {optimized}"
1397 );
1398 }
1399 Ok(())
1400 }
1401
1402 #[test]
1403 fn test_simplify_null_fill_semantic_equivalence() -> VortexResult<()> {
1404 let mut ctx = SESSION.create_execution_ctx();
1405 let array = PrimitiveArray::from_option_iter([Some(1i64), None, Some(3)]).into_array();
1407 let scope = DType::Primitive(PType::I64, Nullability::Nullable);
1408 let null_fill = lit(Scalar::null(DType::Primitive(
1409 PType::I64,
1410 Nullability::Nullable,
1411 )));
1412
1413 let original = case_when(is_null(root()), null_fill, root());
1414 let optimized = original.bind(&scope)?.optimize_recursive()?;
1415 assert_eq!(
1416 optimized.to_string(),
1417 "$",
1418 "expected collapse to root, got {optimized}"
1419 );
1420
1421 let expected = PrimitiveArray::from_option_iter([Some(1i64), None, Some(3)]).into_array();
1422 assert_arrays_eq!(evaluate_expr(&original, &array), expected, &mut ctx);
1423 assert_arrays_eq!(evaluate_bound_expr(&optimized, &array), expected, &mut ctx);
1424 Ok(())
1425 }
1426
1427 #[test]
1428 fn test_simplify_does_not_fire_without_else() -> VortexResult<()> {
1429 let expr = case_when_no_else(is_null(col("x")), lit(0i64));
1430 let optimized = expr
1431 .bind(&nullable_i64_scope(&["x"]))?
1432 .optimize_recursive()?;
1433 assert!(
1434 !optimized.to_string().contains("fill_null"),
1435 "must not rewrite a no-ELSE case_when, got {optimized}"
1436 );
1437 Ok(())
1438 }
1439
1440 #[test]
1441 fn test_simplify_does_not_fire_for_multi_pair() -> VortexResult<()> {
1442 let expr = nested_case_when(
1443 vec![
1444 (is_null(col("x")), lit(0i64)),
1445 (gt(col("x"), lit(5i64)), lit(1i64)),
1446 ],
1447 Some(col("x")),
1448 );
1449 let optimized = expr
1450 .bind(&nullable_i64_scope(&["x"]))?
1451 .optimize_recursive()?;
1452 assert!(
1453 !optimized.to_string().contains("fill_null"),
1454 "must not rewrite a multi-pair case_when, got {optimized}"
1455 );
1456 Ok(())
1457 }
1458
1459 #[test]
1460 fn test_simplify_semantic_equivalence() -> VortexResult<()> {
1461 let mut ctx = SESSION.create_execution_ctx();
1462 let array = PrimitiveArray::from_option_iter([Some(1i64), None, Some(3)]).into_array();
1464 let scope = DType::Primitive(PType::I64, Nullability::Nullable);
1465
1466 let original = case_when(is_null(root()), lit(0i64), root());
1467 let optimized = original.bind(&scope)?.optimize_recursive()?;
1468 assert!(
1469 optimized.to_string().starts_with("vortex.fill_null"),
1470 "expected fill_null, got {optimized}"
1471 );
1472
1473 assert_arrays_eq!(
1476 evaluate_expr(&original, &array),
1477 PrimitiveArray::from_option_iter([Some(1i64), Some(0), Some(3)]).into_array(),
1478 &mut ctx
1479 );
1480 assert_arrays_eq!(
1481 evaluate_bound_expr(&optimized, &array),
1482 buffer![1i64, 0, 3].into_array(),
1483 &mut ctx
1484 );
1485 Ok(())
1486 }
1487
1488 #[test]
1489 fn test_merge_case_branches_alternating_mask() -> VortexResult<()> {
1490 let mut ctx = SESSION.create_execution_ctx();
1491 let n = 100usize;
1494
1495 let branch0_mask = Mask::from_indices(n, (0..n).step_by(2));
1497 let branch1_mask = Mask::from_indices(n, (1..n).step_by(2));
1498
1499 let result = merge_case_branches(
1500 vec![
1501 (
1502 branch0_mask,
1503 PrimitiveArray::from_option_iter(vec![Some(0i32); n]).into_array(),
1504 ),
1505 (
1506 branch1_mask,
1507 PrimitiveArray::from_option_iter(vec![Some(1i32); n]).into_array(),
1508 ),
1509 ],
1510 PrimitiveArray::from_option_iter(vec![Some(99i32); n]).into_array(),
1511 &mut SESSION.create_execution_ctx(),
1512 )?;
1513
1514 let expected: Vec<Option<i32>> = (0..n)
1516 .map(|v| if v % 2 == 0 { Some(0) } else { Some(1) })
1517 .collect();
1518 assert_arrays_eq!(
1519 result,
1520 PrimitiveArray::from_option_iter(expected).into_array(),
1521 &mut ctx
1522 );
1523 Ok(())
1524 }
1525}