Skip to main content

datafusion_physical_expr/expressions/
case.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18mod literal_lookup_table;
19
20use super::{Column, Literal};
21use crate::PhysicalExpr;
22use crate::expressions::{
23    CastExpr, LambdaVariable, NegativeExpr, NotExpr, lit, try_cast,
24};
25use arrow::array::*;
26use arrow::compute::kernels::zip::zip;
27use arrow::compute::{
28    FilterBuilder, FilterPredicate, is_not_null, not, nullif, prep_null_mask_filter,
29};
30use arrow::datatypes::{DataType, Schema, UInt32Type, UnionMode};
31use arrow::error::ArrowError;
32use datafusion_common::cast::as_boolean_array;
33use datafusion_common::{
34    DataFusionError, Result, ScalarValue, assert_or_internal_err, exec_err,
35    internal_datafusion_err, internal_err,
36};
37use datafusion_expr::ColumnarValue;
38use indexmap::IndexMap;
39use std::borrow::Cow;
40use std::collections::BTreeSet;
41use std::hash::Hash;
42use std::sync::Arc;
43
44use crate::expressions::case::literal_lookup_table::LiteralLookupTable;
45use arrow::compute::kernels::merge::{MergeIndex, merge, merge_n};
46use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRecursion};
47use datafusion_physical_expr_common::datum::compare_with_eq;
48use datafusion_physical_expr_common::utils::scatter;
49use itertools::Itertools;
50use std::fmt::{Debug, Formatter};
51
52pub(super) type WhenThen = (Arc<dyn PhysicalExpr>, Arc<dyn PhysicalExpr>);
53
54#[derive(Debug, Hash, PartialEq, Eq)]
55enum EvalMethod {
56    /// CASE WHEN condition THEN result
57    ///      [WHEN ...]
58    ///      [ELSE result]
59    /// END
60    NoExpression(ProjectedCaseBody),
61    /// CASE expression
62    ///     WHEN value THEN result
63    ///     [WHEN ...]
64    ///     [ELSE result]
65    /// END
66    WithExpression(ProjectedCaseBody),
67    /// This is a specialization for a specific use case where we can take a fast path
68    /// for expressions that are infallible and can be cheaply computed for the entire
69    /// record batch rather than just for the rows where the predicate is true.
70    ///
71    /// CASE WHEN condition THEN infallible_expression [ELSE NULL] END
72    InfallibleExprOrNull,
73    /// This is a specialization for a specific use case where we can take a fast path
74    /// if there is just one when/then pair and both the `then` and `else` expressions
75    /// are literal values
76    /// CASE WHEN condition THEN literal ELSE literal END
77    ScalarOrScalar,
78    /// This is a specialization for a specific use case where we can take a fast path
79    /// if there is just one when/then pair, the `then` is an expression, and `else` is either
80    /// an expression, literal NULL or absent.
81    ///
82    /// In contrast to [`EvalMethod::InfallibleExprOrNull`], this specialization can handle fallible
83    /// `then` expressions.
84    ///
85    /// CASE WHEN condition THEN expression [ELSE expression] END
86    ExpressionOrExpression(ProjectedCaseBody),
87
88    /// This is a specialization for [`EvalMethod::WithExpression`] when the value and results are literals
89    ///
90    /// See [`LiteralLookupTable`] for more details
91    WithExprScalarLookupTable(LiteralLookupTable),
92}
93
94/// Implementing hash so we can use `derive` on [`EvalMethod`].
95///
96/// not implementing actual [`Hash`] as it is not dyn compatible so we cannot implement it for
97/// `dyn` [`literal_lookup_table::WhenLiteralIndexMap`].
98///
99/// So implementing empty hash is still valid as the data is derived from `PhysicalExpr` s which are already hashed
100impl Hash for LiteralLookupTable {
101    fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
102}
103
104/// Implementing Equal so we can use `derive` on [`EvalMethod`].
105///
106/// not implementing actual [`PartialEq`] as it is not dyn compatible so we cannot implement it for
107/// `dyn` [`literal_lookup_table::WhenLiteralIndexMap`].
108///
109/// So we always return true as the data is derived from `PhysicalExpr` s which are already compared
110impl PartialEq for LiteralLookupTable {
111    fn eq(&self, _other: &Self) -> bool {
112        true
113    }
114}
115
116impl Eq for LiteralLookupTable {}
117
118/// The body of a CASE expression which consists of an optional base expression, the "when/then"
119/// branches and an optional "else" branch.
120#[derive(Debug, Hash, PartialEq, Eq)]
121struct CaseBody {
122    /// Optional base expression that can be compared to literal values in the "when" expressions
123    expr: Option<Arc<dyn PhysicalExpr>>,
124    /// One or more when/then expressions
125    when_then_expr: Vec<WhenThen>,
126    /// Optional "else" expression
127    else_expr: Option<Arc<dyn PhysicalExpr>>,
128}
129
130impl CaseBody {
131    /// Derives a [ProjectedCaseBody] from this [CaseBody].
132    fn project(&self) -> Result<ProjectedCaseBody> {
133        // Determine the set of columns that are used in all the expressions of the case body.
134        // Use an ordered set so lambda variables continue to be positioned after columns
135        let mut used_column_indices = BTreeSet::<usize>::new();
136        let mut collect_column_indices = |expr: &Arc<dyn PhysicalExpr>| {
137            expr.apply(|expr| {
138                if let Some(column) = expr.downcast_ref::<Column>() {
139                    used_column_indices.insert(column.index());
140                } else if let Some(lambda_variable) =
141                    expr.downcast_ref::<LambdaVariable>()
142                {
143                    used_column_indices.insert(lambda_variable.index());
144                }
145                Ok(TreeNodeRecursion::Continue)
146            })
147            .expect("Closure cannot fail");
148        };
149
150        if let Some(e) = &self.expr {
151            collect_column_indices(e);
152        }
153        self.when_then_expr.iter().for_each(|(w, t)| {
154            collect_column_indices(w);
155            collect_column_indices(t);
156        });
157        if let Some(e) = &self.else_expr {
158            collect_column_indices(e);
159        }
160
161        // Construct a mapping from the original column index to the projected column index.
162        let column_index_map = used_column_indices
163            .iter()
164            .enumerate()
165            .map(|(projected, original)| (*original, projected))
166            .collect::<IndexMap<usize, usize>>();
167
168        // Construct the projected body by rewriting each expression from the original body
169        // using the column index mapping.
170        let project = |expr: &Arc<dyn PhysicalExpr>| -> Result<Arc<dyn PhysicalExpr>> {
171            Arc::clone(expr)
172                .transform_down(|e| {
173                    if let Some(column) = e.downcast_ref::<Column>() {
174                        let original = column.index();
175                        let projected = *column_index_map.get(&original).unwrap();
176                        if projected != original {
177                            return Ok(Transformed::yes(Arc::new(Column::new(
178                                column.name(),
179                                projected,
180                            ))));
181                        }
182                    } else if let Some(lambda_variable) =
183                        e.downcast_ref::<LambdaVariable>()
184                    {
185                        let original = lambda_variable.index();
186                        let projected = *column_index_map.get(&original).unwrap();
187                        if projected != original {
188                            return Ok(Transformed::yes(Arc::new(LambdaVariable::new(
189                                projected,
190                                Arc::clone(lambda_variable.field()),
191                            ))));
192                        }
193                    }
194                    Ok(Transformed::no(e))
195                })
196                .map(|t| t.data)
197        };
198
199        let projected_body = CaseBody {
200            expr: self.expr.as_ref().map(project).transpose()?,
201            when_then_expr: self
202                .when_then_expr
203                .iter()
204                .map(|(e, t)| Ok((project(e)?, project(t)?)))
205                .collect::<Result<Vec<_>>>()?,
206            else_expr: self.else_expr.as_ref().map(project).transpose()?,
207        };
208
209        // Construct the projection vector
210        let projection = column_index_map
211            .iter()
212            .sorted_by_key(|(_, v)| **v)
213            .map(|(k, _)| *k)
214            .collect::<Vec<_>>();
215
216        Ok(ProjectedCaseBody {
217            projection,
218            body: projected_body,
219        })
220    }
221}
222
223/// A derived case body that can be used to evaluate a case expression after projecting
224/// record batches using a projection vector.
225///
226/// This is used to avoid filtering columns that are not used in the
227/// input `RecordBatch` when progressively evaluating a `CASE` expression's
228/// remainder batches. Filtering these columns is wasteful since for a record
229/// batch of `n` rows, filtering requires at worst a copy of `n - 1` values
230/// per array. If these filtered values will never be accessed, the time spent
231/// producing them is better avoided.
232///
233/// For example, if we are evaluating the following case expression that
234/// only references columns B and D:
235///
236/// ```sql
237/// SELECT CASE WHEN B > 10 THEN D ELSE NULL END FROM (VALUES (...)) T(A, B, C, D)
238/// ```
239///
240/// Of the 4 input columns `[A, B, C, D]`, the `CASE` expression only access `B` and `D`.
241/// Filtering `A` and `C` would be unnecessary and wasteful.
242///
243/// If we only retain columns `B` and `D` using `RecordBatch::project` and the projection vector
244/// `[1, 3]`, the indices of these two columns will change to `[0, 1]`. To evaluate the
245/// case expression, it will need to be rewritten from `CASE WHEN B@1 > 10 THEN D@3 ELSE NULL END`
246/// to `CASE WHEN B@0 > 10 THEN D@1 ELSE NULL END`.
247///
248/// The projection vector and the rewritten expression (which only differs from the original in
249/// column reference indices) are held in a `ProjectedCaseBody`.
250#[derive(Debug, Hash, PartialEq, Eq)]
251struct ProjectedCaseBody {
252    projection: Vec<usize>,
253    body: CaseBody,
254}
255
256/// The CASE expression is similar to a series of nested if/else and there are two forms that
257/// can be used. The first form consists of a series of boolean "when" expressions with
258/// corresponding "then" expressions, and an optional "else" expression.
259///
260/// CASE WHEN condition THEN result
261///      [WHEN ...]
262///      [ELSE result]
263/// END
264///
265/// The second form uses a base expression and then a series of "when" clauses that match on a
266/// literal value.
267///
268/// CASE expression
269///     WHEN value THEN result
270///     [WHEN ...]
271///     [ELSE result]
272/// END
273#[derive(Debug)]
274pub struct CaseExpr {
275    /// The case expression body
276    body: CaseBody,
277    /// Evaluation method to use
278    eval_method: EvalMethod,
279}
280
281// eval_method is functionally derived from body, so excluding it from
282// Hash/Eq avoids redundantly hashing the expression tree twice. For
283// nested CASE chains this prevents exponential blowup (see #22173).
284impl Hash for CaseExpr {
285    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
286        self.body.hash(state);
287    }
288}
289
290impl PartialEq for CaseExpr {
291    fn eq(&self, other: &Self) -> bool {
292        self.body == other.body
293    }
294}
295
296impl Eq for CaseExpr {}
297
298impl std::fmt::Display for CaseExpr {
299    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
300        write!(f, "CASE ")?;
301        if let Some(e) = &self.body.expr {
302            write!(f, "{e} ")?;
303        }
304        for (w, t) in &self.body.when_then_expr {
305            write!(f, "WHEN {w} THEN {t} ")?;
306        }
307        if let Some(e) = &self.body.else_expr {
308            write!(f, "ELSE {e} ")?;
309        }
310        write!(f, "END")
311    }
312}
313
314/// This is a specialization for a specific use case where we can take a fast path
315/// for expressions that are infallible and can be cheaply computed for the entire
316/// record batch rather than just for the rows where the predicate is true. For now,
317/// this is limited to use with Column expressions but could potentially be used for other
318/// expressions in the future
319fn is_cheap_and_infallible(expr: &Arc<dyn PhysicalExpr>) -> bool {
320    expr.is::<Column>()
321}
322
323/// Creates a [FilterPredicate] from a boolean array.
324fn create_filter(predicate: &BooleanArray, optimize: bool) -> FilterPredicate {
325    let mut filter_builder = FilterBuilder::new(predicate);
326    if optimize {
327        // Always optimize the filter since we use them multiple times.
328        filter_builder = filter_builder.optimize();
329    }
330    filter_builder.build()
331}
332
333fn multiple_arrays(data_type: &DataType) -> bool {
334    match data_type {
335        DataType::Struct(fields) => {
336            fields.len() > 1
337                || fields.len() == 1 && multiple_arrays(fields[0].data_type())
338        }
339        DataType::Union(fields, UnionMode::Sparse) => !fields.is_empty(),
340        _ => false,
341    }
342}
343
344// This should be removed when https://github.com/apache/arrow-rs/pull/8693
345// is merged and becomes available.
346fn filter_record_batch(
347    record_batch: &RecordBatch,
348    filter: &FilterPredicate,
349) -> std::result::Result<RecordBatch, ArrowError> {
350    let filtered_columns = record_batch
351        .columns()
352        .iter()
353        .map(|a| filter_array(a, filter))
354        .collect::<std::result::Result<Vec<_>, _>>()?;
355    // SAFETY: since we start from a valid RecordBatch, there's no need to revalidate the schema
356    // since the set of columns has not changed.
357    // The input column arrays all had the same length (since they're coming from a valid RecordBatch)
358    // and the filtering them with the same filter will produces a new set of arrays with identical
359    // lengths.
360    unsafe {
361        Ok(RecordBatch::new_unchecked(
362            record_batch.schema(),
363            filtered_columns,
364            filter.count(),
365        ))
366    }
367}
368
369// This function exists purely to be able to use the same call style
370// for `filter_record_batch` and `filter_array` at the point of use.
371// When https://github.com/apache/arrow-rs/pull/8693 is available, replace
372// both with method calls on `FilterPredicate`.
373#[inline(always)]
374fn filter_array(
375    array: &dyn Array,
376    filter: &FilterPredicate,
377) -> std::result::Result<ArrayRef, ArrowError> {
378    filter.filter(array)
379}
380
381/// An index into the partial results array that's more compact than `usize`.
382///
383/// `u32::MAX` is reserved as a special 'none' value. This is used instead of
384/// `Option` to keep the array of indices as compact as possible.
385#[derive(Copy, Clone, PartialEq, Eq)]
386struct PartialResultIndex {
387    index: u32,
388}
389
390const NONE_VALUE: u32 = u32::MAX;
391
392impl PartialResultIndex {
393    /// Returns the 'none' placeholder value.
394    fn none() -> Self {
395        Self { index: NONE_VALUE }
396    }
397
398    fn zero() -> Self {
399        Self { index: 0 }
400    }
401
402    /// Creates a new partial result index.
403    ///
404    /// If the provided value is greater than or equal to `u32::MAX`
405    /// an error will be returned.
406    fn try_new(index: usize) -> Result<Self> {
407        let Ok(index) = u32::try_from(index) else {
408            return internal_err!("Partial result index exceeds limit");
409        };
410
411        assert_or_internal_err!(
412            index != NONE_VALUE,
413            "Partial result index exceeds limit"
414        );
415
416        Ok(Self { index })
417    }
418
419    /// Determines if this index is the 'none' placeholder value or not.
420    fn is_none(&self) -> bool {
421        self.index == NONE_VALUE
422    }
423}
424
425impl MergeIndex for PartialResultIndex {
426    /// Returns `Some(index)` if this value is not the 'none' placeholder, `None` otherwise.
427    fn index(&self) -> Option<usize> {
428        if self.is_none() {
429            None
430        } else {
431            Some(self.index as usize)
432        }
433    }
434}
435
436impl Debug for PartialResultIndex {
437    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
438        if self.is_none() {
439            write!(f, "null")
440        } else {
441            write!(f, "{}", self.index)
442        }
443    }
444}
445
446enum ResultState {
447    /// The final result is an array containing only null values.
448    Empty,
449    /// The final result needs to be computed by merging the data in `arrays`.
450    Partial {
451        // A `Vec` of partial results that should be merged.
452        // `partial_result_indices` contains indexes into this vec.
453        arrays: Vec<ArrayRef>,
454        // Indicates per result row from which array in `partial_results` a value should be taken.
455        indices: Vec<PartialResultIndex>,
456    },
457    /// A single branch matched all input rows. When creating the final result, no further merging
458    /// of partial results is necessary.
459    Complete(ColumnarValue),
460}
461
462/// A builder for constructing result arrays for CASE expressions.
463///
464/// Rather than building a monolithic array containing all results, it maintains a set of
465/// partial result arrays and a mapping that indicates for each row which partial array
466/// contains the result value for that row.
467///
468/// On finish(), the builder will merge all partial results into a single array if necessary.
469/// If all rows evaluated to the same array, that array can be returned directly without
470/// any merging overhead.
471struct ResultBuilder {
472    data_type: DataType,
473    /// The number of rows in the final result.
474    row_count: usize,
475    state: ResultState,
476}
477
478impl ResultBuilder {
479    /// Creates a new ResultBuilder that will produce arrays of the given data type.
480    ///
481    /// The `row_count` parameter indicates the number of rows in the final result.
482    fn new(data_type: &DataType, row_count: usize) -> Self {
483        Self {
484            data_type: data_type.clone(),
485            row_count,
486            state: ResultState::Empty,
487        }
488    }
489
490    /// Adds a result for one branch of the case expression.
491    ///
492    /// `row_indices` should be a [UInt32Array] containing [RecordBatch] relative row indices
493    /// for which `value` contains result values.
494    ///
495    /// If `value` is a scalar, the scalar value will be used as the value for each row in `row_indices`.
496    ///
497    /// If `value` is an array, the values from the array and the indices from `row_indices` will be
498    /// processed pairwise. The lengths of `value` and `row_indices` must match.
499    ///
500    /// The diagram below shows a situation where a when expression matched rows 1 and 4 of the
501    /// record batch. The then expression produced the value array `[A, D]`.
502    /// After adding this result, the result array will have been added to `partial arrays` and
503    /// `partial indices` will have been updated at indexes `1` and `4`.
504    ///
505    /// ```text
506    ///  ┌─────────┐     ┌─────────┐┌───────────┐                            ┌─────────┐┌───────────┐
507    ///  │    C    │     │ 0: None ││┌ 0 ──────┐│                            │ 0: None ││┌ 0 ──────┐│
508    ///  ├─────────┤     ├─────────┤││    A    ││                            ├─────────┤││    A    ││
509    ///  │    D    │     │ 1: None ││└─────────┘│                            │ 1:  2   ││└─────────┘│
510    ///  └─────────┘     ├─────────┤│┌ 1 ──────┐│   add_branch_result(       ├─────────┤│┌ 1 ──────┐│
511    ///   matching       │ 2:  0   │││    B    ││     row indices,           │ 2:  0   │││    B    ││
512    /// 'then' values    ├─────────┤│└─────────┘│     value                  ├─────────┤│└─────────┘│
513    ///                  │ 3: None ││           │   )                        │ 3: None ││┌ 2 ──────┐│
514    ///  ┌─────────┐     ├─────────┤│           │ ─────────────────────────▶ ├─────────┤││    C    ││
515    ///  │    1    │     │ 4: None ││           │                            │ 4:  2   ││├─────────┤│
516    ///  ├─────────┤     ├─────────┤│           │                            ├─────────┤││    D    ││
517    ///  │    4    │     │ 5:  1   ││           │                            │ 5:  1   ││└─────────┘│
518    ///  └─────────┘     └─────────┘└───────────┘                            └─────────┘└───────────┘
519    /// row indices        partial     partial                                 partial     partial
520    ///                    indices     arrays                                  indices     arrays
521    /// ```
522    fn add_branch_result(
523        &mut self,
524        row_indices: &ArrayRef,
525        value: ColumnarValue,
526    ) -> Result<()> {
527        match value {
528            ColumnarValue::Array(a) => {
529                if a.len() != row_indices.len() {
530                    internal_err!("Array length must match row indices length")
531                } else if row_indices.len() == self.row_count {
532                    self.set_complete_result(ColumnarValue::Array(a))
533                } else {
534                    self.add_partial_result(row_indices, a)
535                }
536            }
537            ColumnarValue::Scalar(s) => {
538                if row_indices.len() == self.row_count {
539                    self.set_complete_result(ColumnarValue::Scalar(s))
540                } else {
541                    self.add_partial_result(
542                        row_indices,
543                        s.to_array_of_size(row_indices.len())?,
544                    )
545                }
546            }
547        }
548    }
549
550    /// Adds a partial result array.
551    ///
552    /// This method adds the given array data as a partial result and updates the index mapping
553    /// to indicate that the specified rows should take their values from this array.
554    /// The partial results will be merged into a single array when finish() is called.
555    fn add_partial_result(
556        &mut self,
557        row_indices: &ArrayRef,
558        row_values: ArrayRef,
559    ) -> Result<()> {
560        assert_or_internal_err!(
561            row_indices.null_count() == 0,
562            "Row indices must not contain nulls"
563        );
564
565        match &mut self.state {
566            ResultState::Empty => {
567                let array_index = PartialResultIndex::zero();
568                let mut indices = vec![PartialResultIndex::none(); self.row_count];
569                for row_ix in row_indices.as_primitive::<UInt32Type>().values().iter() {
570                    indices[*row_ix as usize] = array_index;
571                }
572
573                self.state = ResultState::Partial {
574                    arrays: vec![row_values],
575                    indices,
576                };
577
578                Ok(())
579            }
580            ResultState::Partial { arrays, indices } => {
581                let array_index = PartialResultIndex::try_new(arrays.len())?;
582
583                arrays.push(row_values);
584
585                for row_ix in row_indices.as_primitive::<UInt32Type>().values().iter() {
586                    // This is check is only active for debug config because the callers of this method,
587                    // `case_when_with_expr` and `case_when_no_expr`, already ensure that
588                    // they only calculate a value for each row at most once.
589                    #[cfg(debug_assertions)]
590                    assert_or_internal_err!(
591                        indices[*row_ix as usize].is_none(),
592                        "Duplicate value for row {}",
593                        *row_ix
594                    );
595
596                    indices[*row_ix as usize] = array_index;
597                }
598                Ok(())
599            }
600            ResultState::Complete(_) => internal_err!(
601                "Cannot add a partial result when complete result is already set"
602            ),
603        }
604    }
605
606    /// Sets a result that applies to all rows.
607    ///
608    /// This is an optimization for cases where all rows evaluate to the same result.
609    /// When a complete result is set, the builder will return it directly from finish()
610    /// without any merging overhead.
611    fn set_complete_result(&mut self, value: ColumnarValue) -> Result<()> {
612        match &self.state {
613            ResultState::Empty => {
614                self.state = ResultState::Complete(value);
615                Ok(())
616            }
617            ResultState::Partial { .. } => {
618                internal_err!(
619                    "Cannot set a complete result when there are already partial results"
620                )
621            }
622            ResultState::Complete(_) => internal_err!("Complete result already set"),
623        }
624    }
625
626    /// Finishes building the result and returns the final array.
627    fn finish(self) -> Result<ColumnarValue> {
628        match self.state {
629            ResultState::Empty => {
630                // No complete result and no partial results.
631                // This can happen for case expressions with no else branch where no rows
632                // matched.
633                Ok(ColumnarValue::Scalar(ScalarValue::try_new_null(
634                    &self.data_type,
635                )?))
636            }
637            ResultState::Partial { arrays, indices } => {
638                // Merge partial results into a single array.
639                let array_refs = arrays.iter().map(|a| a.as_ref()).collect::<Vec<_>>();
640                Ok(ColumnarValue::Array(merge_n(&array_refs, &indices)?))
641            }
642            ResultState::Complete(v) => {
643                // If we have a complete result, we can just return it.
644                Ok(v)
645            }
646        }
647    }
648}
649
650impl CaseExpr {
651    /// Create a new CASE WHEN expression
652    pub fn try_new(
653        expr: Option<Arc<dyn PhysicalExpr>>,
654        when_then_expr: Vec<WhenThen>,
655        else_expr: Option<Arc<dyn PhysicalExpr>>,
656    ) -> Result<Self> {
657        // normalize null literals to None in the else_expr (this already happens
658        // during SQL planning, but not necessarily for other use cases)
659        let else_expr = match &else_expr {
660            Some(e) => match e.downcast_ref::<Literal>() {
661                Some(lit) if lit.value().is_null() => None,
662                _ => else_expr,
663            },
664            _ => else_expr,
665        };
666
667        if when_then_expr.is_empty() {
668            return exec_err!("There must be at least one WHEN clause");
669        }
670
671        let body = CaseBody {
672            expr,
673            when_then_expr,
674            else_expr,
675        };
676
677        let eval_method = Self::find_best_eval_method(&body)?;
678
679        Ok(Self { body, eval_method })
680    }
681
682    fn find_best_eval_method(body: &CaseBody) -> Result<EvalMethod> {
683        if body.expr.is_some() {
684            if let Some(mapping) = LiteralLookupTable::maybe_new(body) {
685                return Ok(EvalMethod::WithExprScalarLookupTable(mapping));
686            }
687
688            return Ok(EvalMethod::WithExpression(body.project()?));
689        }
690
691        Ok(
692            if body.when_then_expr.len() == 1
693                && is_cheap_and_infallible(&(body.when_then_expr[0].1))
694                && body.else_expr.is_none()
695            {
696                EvalMethod::InfallibleExprOrNull
697            } else if body.when_then_expr.len() == 1
698                && body.when_then_expr[0].1.is::<Literal>()
699                && body.else_expr.is_some()
700                && body.else_expr.as_ref().unwrap().is::<Literal>()
701            {
702                EvalMethod::ScalarOrScalar
703            } else if body.when_then_expr.len() == 1 {
704                EvalMethod::ExpressionOrExpression(body.project()?)
705            } else {
706                EvalMethod::NoExpression(body.project()?)
707            },
708        )
709    }
710
711    /// Optional base expression that can be compared to literal values in the "when" expressions
712    pub fn expr(&self) -> Option<&Arc<dyn PhysicalExpr>> {
713        self.body.expr.as_ref()
714    }
715
716    /// One or more when/then expressions
717    pub fn when_then_expr(&self) -> &[WhenThen] {
718        &self.body.when_then_expr
719    }
720
721    /// Optional "else" expression
722    pub fn else_expr(&self) -> Option<&Arc<dyn PhysicalExpr>> {
723        self.body.else_expr.as_ref()
724    }
725}
726
727impl CaseBody {
728    fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
729        // since all then results have the same data type, we can choose any one as the
730        // return data type except for the null.
731        let mut data_type = DataType::Null;
732        for i in 0..self.when_then_expr.len() {
733            data_type = self.when_then_expr[i].1.data_type(input_schema)?;
734            if !data_type.equals_datatype(&DataType::Null) {
735                break;
736            }
737        }
738        // if all then results are null, we use data type of else expr instead if possible.
739        if data_type.equals_datatype(&DataType::Null)
740            && let Some(e) = &self.else_expr
741        {
742            data_type = e.data_type(input_schema)?;
743        }
744
745        Ok(data_type)
746    }
747
748    /// See [CaseExpr::case_when_with_expr].
749    fn case_when_with_expr(
750        &self,
751        batch: &RecordBatch,
752        return_type: &DataType,
753    ) -> Result<ColumnarValue> {
754        let mut result_builder = ResultBuilder::new(return_type, batch.num_rows());
755
756        // `remainder_rows` contains the indices of the rows that need to be evaluated
757        let mut remainder_rows: ArrayRef =
758            Arc::new(UInt32Array::from_iter_values(0..batch.num_rows() as u32));
759        // `remainder_batch` contains the rows themselves that need to be evaluated
760        let mut remainder_batch = Cow::Borrowed(batch);
761
762        // evaluate the base expression
763        let mut base_values = self
764            .expr
765            .as_ref()
766            .unwrap()
767            .evaluate(batch)?
768            .into_array(batch.num_rows())?;
769
770        // Fill in a result value already for rows where the base expression value is null
771        // Since each when expression is tested against the base expression using the equality
772        // operator, null base values can never match any when expression. `x = NULL` is falsy,
773        // for all possible values of `x`.
774        let base_null_count = base_values.logical_null_count();
775        if base_null_count > 0 {
776            // Use `is_not_null` since this is a cheap clone of the null buffer from 'base_value'.
777            // We already checked there are nulls, so we can be sure a new buffer will not be
778            // created.
779            let base_not_nulls = is_not_null(base_values.as_ref())?;
780            let base_all_null = base_null_count == remainder_batch.num_rows();
781
782            // If there is an else expression, use that as the default value for the null rows
783            // Otherwise the default `null` value from the result builder will be used.
784            if let Some(e) = &self.else_expr {
785                let expr = try_cast(Arc::clone(e), &batch.schema(), return_type.clone())?;
786
787                if base_all_null {
788                    // All base values were null, so no need to filter
789                    let nulls_value = expr.evaluate(&remainder_batch)?;
790                    result_builder.add_branch_result(&remainder_rows, nulls_value)?;
791                } else {
792                    // Filter out the null rows and evaluate the else expression for those
793                    let nulls_filter = create_filter(&not(&base_not_nulls)?, true);
794                    let nulls_batch =
795                        filter_record_batch(&remainder_batch, &nulls_filter)?;
796                    let nulls_rows = filter_array(&remainder_rows, &nulls_filter)?;
797                    let nulls_value = expr.evaluate(&nulls_batch)?;
798                    result_builder.add_branch_result(&nulls_rows, nulls_value)?;
799                }
800            }
801
802            // All base values are null, so we can return early
803            if base_all_null {
804                return result_builder.finish();
805            }
806
807            // Remove the null rows from the remainder batch
808            let not_null_filter = create_filter(&base_not_nulls, true);
809            remainder_batch =
810                Cow::Owned(filter_record_batch(&remainder_batch, &not_null_filter)?);
811            remainder_rows = filter_array(&remainder_rows, &not_null_filter)?;
812            base_values = filter_array(&base_values, &not_null_filter)?;
813        }
814
815        // The types of case and when expressions will be coerced to match.
816        // We only need to check if the base_value is nested.
817        let base_value_is_nested = base_values.data_type().is_nested();
818
819        for i in 0..self.when_then_expr.len() {
820            // Evaluate the 'when' predicate for the remainder batch
821            // This results in a boolean array with the same length as the remaining number of rows
822            let when_expr = &self.when_then_expr[i].0;
823            let when_value = match when_expr.evaluate(&remainder_batch)? {
824                ColumnarValue::Array(a) => {
825                    compare_with_eq(&a, &base_values, base_value_is_nested)
826                }
827                ColumnarValue::Scalar(s) => {
828                    compare_with_eq(&s.to_scalar()?, &base_values, base_value_is_nested)
829                }
830            }?;
831
832            // If the 'when' predicate did not match any rows, continue to the next branch immediately.
833            // Only counts valid slots that are true (masked-null predicate slots are ignored),
834            // so no `prep_null_mask_filter` needed here.
835            if !when_value.has_true() {
836                continue;
837            }
838
839            // If the 'when' predicate matched all remaining rows, there is no need to filter
840            if when_value.null_count() == 0 && !when_value.has_false() {
841                let then_expression = &self.when_then_expr[i].1;
842                let then_value = then_expression.evaluate(&remainder_batch)?;
843                result_builder.add_branch_result(&remainder_rows, then_value)?;
844                return result_builder.finish();
845            }
846
847            // Filter the remainder batch based on the 'when' value
848            // This results in a batch containing only the rows that need to be evaluated
849            // for the current branch
850            // Still no need to call `prep_null_mask_filter` since `create_filter` will already do
851            // this unconditionally.
852            let then_filter = create_filter(&when_value, true);
853            let then_batch = filter_record_batch(&remainder_batch, &then_filter)?;
854            let then_rows = filter_array(&remainder_rows, &then_filter)?;
855
856            let then_expression = &self.when_then_expr[i].1;
857            let then_value = then_expression.evaluate(&then_batch)?;
858            result_builder.add_branch_result(&then_rows, then_value)?;
859
860            // If this is the last 'when' branch and there is no 'else' expression, there's no
861            // point in calculating the remaining rows.
862            if self.else_expr.is_none() && i == self.when_then_expr.len() - 1 {
863                return result_builder.finish();
864            }
865
866            // Prepare the next when branch (or the else branch)
867            let next_selection = match when_value.null_count() {
868                0 => not(&when_value),
869                _ => {
870                    // `prep_null_mask_filter` is required to ensure the not operation treats nulls
871                    // as false
872                    not(&prep_null_mask_filter(&when_value))
873                }
874            }?;
875            let next_filter = create_filter(&next_selection, true);
876            remainder_batch =
877                Cow::Owned(filter_record_batch(&remainder_batch, &next_filter)?);
878            remainder_rows = filter_array(&remainder_rows, &next_filter)?;
879            base_values = filter_array(&base_values, &next_filter)?;
880        }
881
882        // If we reached this point, some rows were left unmatched.
883        // Check if those need to be evaluated using the 'else' expression.
884        if let Some(e) = &self.else_expr {
885            // keep `else_expr`'s data type and return type consistent
886            let expr = try_cast(Arc::clone(e), &batch.schema(), return_type.clone())?;
887            let else_value = expr.evaluate(&remainder_batch)?;
888            result_builder.add_branch_result(&remainder_rows, else_value)?;
889        }
890
891        result_builder.finish()
892    }
893
894    /// See [CaseExpr::case_when_no_expr].
895    fn case_when_no_expr(
896        &self,
897        batch: &RecordBatch,
898        return_type: &DataType,
899    ) -> Result<ColumnarValue> {
900        let mut result_builder = ResultBuilder::new(return_type, batch.num_rows());
901
902        // `remainder_rows` contains the indices of the rows that need to be evaluated
903        let mut remainder_rows: ArrayRef =
904            Arc::new(UInt32Array::from_iter(0..batch.num_rows() as u32));
905        // `remainder_batch` contains the rows themselves that need to be evaluated
906        let mut remainder_batch = Cow::Borrowed(batch);
907
908        for i in 0..self.when_then_expr.len() {
909            // Evaluate the 'when' predicate for the remainder batch
910            // This results in a boolean array with the same length as the remaining number of rows
911            let when_predicate = &self.when_then_expr[i].0;
912            let when_value = when_predicate
913                .evaluate(&remainder_batch)?
914                .into_array(remainder_batch.num_rows())?;
915            let when_value = as_boolean_array(&when_value).map_err(|_| {
916                internal_datafusion_err!("WHEN expression did not return a BooleanArray")
917            })?;
918
919            // If the 'when' predicate did not match any rows, continue to the next branch immediately.
920            // Only counts valid slots that are true (masked-null predicate slots are ignored)
921            // so no `prep_null_mask_filter` needed here.
922            if !when_value.has_true() {
923                continue;
924            }
925
926            // If the 'when' predicate matched all remaining rows, there is no need to filter
927            if when_value.null_count() == 0 && !when_value.has_false() {
928                let then_expression = &self.when_then_expr[i].1;
929                let then_value = then_expression.evaluate(&remainder_batch)?;
930                result_builder.add_branch_result(&remainder_rows, then_value)?;
931                return result_builder.finish();
932            }
933
934            // Filter the remainder batch based on the 'when' value
935            // This results in a batch containing only the rows that need to be evaluated
936            // for the current branch
937            // Still no need to call `prep_null_mask_filter` since `create_filter` will already do
938            // this unconditionally.
939            let then_filter = create_filter(when_value, true);
940            let then_batch = filter_record_batch(&remainder_batch, &then_filter)?;
941            let then_rows = filter_array(&remainder_rows, &then_filter)?;
942
943            let then_expression = &self.when_then_expr[i].1;
944            let then_value = then_expression.evaluate(&then_batch)?;
945            result_builder.add_branch_result(&then_rows, then_value)?;
946
947            // If this is the last 'when' branch and there is no 'else' expression, there's no
948            // point in calculating the remaining rows.
949            if self.else_expr.is_none() && i == self.when_then_expr.len() - 1 {
950                return result_builder.finish();
951            }
952
953            // Prepare the next when branch (or the else branch)
954            let next_selection = match when_value.null_count() {
955                0 => not(when_value),
956                _ => {
957                    // `prep_null_mask_filter` is required to ensure the not operation treats nulls
958                    // as false
959                    not(&prep_null_mask_filter(when_value))
960                }
961            }?;
962            let next_filter = create_filter(&next_selection, true);
963            remainder_batch =
964                Cow::Owned(filter_record_batch(&remainder_batch, &next_filter)?);
965            remainder_rows = filter_array(&remainder_rows, &next_filter)?;
966        }
967
968        // If we reached this point, some rows were left unmatched.
969        // Check if those need to be evaluated using the 'else' expression.
970        if let Some(e) = &self.else_expr {
971            // keep `else_expr`'s data type and return type consistent
972            let expr = try_cast(Arc::clone(e), &batch.schema(), return_type.clone())?;
973            let else_value = expr.evaluate(&remainder_batch)?;
974            result_builder.add_branch_result(&remainder_rows, else_value)?;
975        }
976
977        result_builder.finish()
978    }
979
980    /// See [CaseExpr::expr_or_expr].
981    fn expr_or_expr(
982        &self,
983        batch: &RecordBatch,
984        when_value: &BooleanArray,
985    ) -> Result<ColumnarValue> {
986        let when_value = match when_value.null_count() {
987            0 => Cow::Borrowed(when_value),
988            _ => {
989                // `prep_null_mask_filter` is required to ensure null is treated as false
990                Cow::Owned(prep_null_mask_filter(when_value))
991            }
992        };
993
994        let optimize_filter = batch.num_columns() > 1
995            || (batch.num_columns() == 1 && multiple_arrays(batch.column(0).data_type()));
996
997        let when_filter = create_filter(&when_value, optimize_filter);
998        let then_batch = filter_record_batch(batch, &when_filter)?;
999        let then_value = self.when_then_expr[0].1.evaluate(&then_batch)?;
1000
1001        match &self.else_expr {
1002            None => {
1003                let then_array = then_value.to_array(when_value.true_count())?;
1004                scatter(&when_value, then_array.as_ref()).map(ColumnarValue::Array)
1005            }
1006            Some(else_expr) => {
1007                let else_selection = not(&when_value)?;
1008                let else_filter = create_filter(&else_selection, optimize_filter);
1009                let else_batch = filter_record_batch(batch, &else_filter)?;
1010
1011                // keep `else_expr`'s data type and return type consistent
1012                let return_type = self.data_type(&batch.schema())?;
1013                let else_expr =
1014                    try_cast(Arc::clone(else_expr), &batch.schema(), return_type.clone())
1015                        .unwrap_or_else(|_| Arc::clone(else_expr));
1016
1017                let else_value = else_expr.evaluate(&else_batch)?;
1018
1019                Ok(ColumnarValue::Array(match (then_value, else_value) {
1020                    (ColumnarValue::Array(t), ColumnarValue::Array(e)) => {
1021                        merge(&when_value, &t, &e)
1022                    }
1023                    (ColumnarValue::Scalar(t), ColumnarValue::Array(e)) => {
1024                        merge(&when_value, &t.to_scalar()?, &e)
1025                    }
1026                    (ColumnarValue::Array(t), ColumnarValue::Scalar(e)) => {
1027                        merge(&when_value, &t, &e.to_scalar()?)
1028                    }
1029                    (ColumnarValue::Scalar(t), ColumnarValue::Scalar(e)) => {
1030                        merge(&when_value, &t.to_scalar()?, &e.to_scalar()?)
1031                    }
1032                }?))
1033            }
1034        }
1035    }
1036}
1037
1038impl CaseExpr {
1039    /// This function evaluates the form of CASE that matches an expression to fixed values.
1040    ///
1041    /// CASE expression
1042    ///     WHEN value THEN result
1043    ///     [WHEN ...]
1044    ///     [ELSE result]
1045    /// END
1046    fn case_when_with_expr(
1047        &self,
1048        batch: &RecordBatch,
1049        projected: &ProjectedCaseBody,
1050    ) -> Result<ColumnarValue> {
1051        let return_type = self.data_type(&batch.schema())?;
1052        // projected.projection may include indexes of lambda variables not available on this batch
1053        let projection = projected
1054            .projection
1055            .iter()
1056            .copied()
1057            .filter(|index| *index < batch.num_columns())
1058            .collect::<Vec<_>>();
1059        if projection.len() < batch.num_columns() {
1060            let projected_batch = batch.project(&projection)?;
1061            projected
1062                .body
1063                .case_when_with_expr(&projected_batch, &return_type)
1064        } else {
1065            self.body.case_when_with_expr(batch, &return_type)
1066        }
1067    }
1068
1069    /// This function evaluates the form of CASE where each WHEN expression is a boolean
1070    /// expression.
1071    ///
1072    /// CASE WHEN condition THEN result
1073    ///      [WHEN ...]
1074    ///      [ELSE result]
1075    /// END
1076    fn case_when_no_expr(
1077        &self,
1078        batch: &RecordBatch,
1079        projected: &ProjectedCaseBody,
1080    ) -> Result<ColumnarValue> {
1081        let return_type = self.data_type(&batch.schema())?;
1082        // projected.projection may include indexes of lambda variables not available on this batch
1083        let projection = projected
1084            .projection
1085            .iter()
1086            .copied()
1087            .filter(|index| *index < batch.num_columns())
1088            .collect::<Vec<_>>();
1089        if projection.len() < batch.num_columns() {
1090            let projected_batch = batch.project(&projection)?;
1091            projected
1092                .body
1093                .case_when_no_expr(&projected_batch, &return_type)
1094        } else {
1095            self.body.case_when_no_expr(batch, &return_type)
1096        }
1097    }
1098
1099    /// This function evaluates the specialized case of:
1100    ///
1101    /// CASE WHEN condition THEN column
1102    ///      [ELSE NULL]
1103    /// END
1104    ///
1105    /// Note that this function is only safe to use for "then" expressions
1106    /// that are infallible because the expression will be evaluated for all
1107    /// rows in the input batch.
1108    fn case_column_or_null(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
1109        let when_expr = &self.body.when_then_expr[0].0;
1110        let then_expr = &self.body.when_then_expr[0].1;
1111
1112        match when_expr.evaluate(batch)? {
1113            // WHEN true --> column
1114            ColumnarValue::Scalar(ScalarValue::Boolean(Some(true))) => {
1115                then_expr.evaluate(batch)
1116            }
1117            // WHEN [false | null] --> NULL
1118            ColumnarValue::Scalar(_) => {
1119                // return scalar NULL value
1120                ScalarValue::try_from(self.data_type(&batch.schema())?)
1121                    .map(ColumnarValue::Scalar)
1122            }
1123            // WHEN column --> column
1124            ColumnarValue::Array(bit_mask) => {
1125                let bit_mask = bit_mask
1126                    .as_any()
1127                    .downcast_ref::<BooleanArray>()
1128                    .expect("predicate should evaluate to a boolean array");
1129                // invert the bitmask
1130                let bit_mask = match bit_mask.null_count() {
1131                    0 => not(bit_mask)?,
1132                    _ => not(&prep_null_mask_filter(bit_mask))?,
1133                };
1134                match then_expr.evaluate(batch)? {
1135                    ColumnarValue::Array(array) => {
1136                        Ok(ColumnarValue::Array(nullif(&array, &bit_mask)?))
1137                    }
1138                    ColumnarValue::Scalar(_) => {
1139                        internal_err!("expression did not evaluate to an array")
1140                    }
1141                }
1142            }
1143        }
1144    }
1145
1146    fn scalar_or_scalar(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
1147        let return_type = self.data_type(&batch.schema())?;
1148
1149        // evaluate when expression
1150        let when_value = self.body.when_then_expr[0].0.evaluate(batch)?;
1151        let when_value = when_value.into_array(batch.num_rows())?;
1152        let when_value = as_boolean_array(&when_value).map_err(|_| {
1153            internal_datafusion_err!("WHEN expression did not return a BooleanArray")
1154        })?;
1155
1156        // Treat 'NULL' as false value
1157        let when_value = match when_value.null_count() {
1158            0 => Cow::Borrowed(when_value),
1159            _ => Cow::Owned(prep_null_mask_filter(when_value)),
1160        };
1161
1162        // evaluate then_value
1163        let then_value = self.body.when_then_expr[0].1.evaluate(batch)?;
1164        let then_value = Scalar::new(then_value.into_array(1)?);
1165
1166        let Some(e) = &self.body.else_expr else {
1167            return internal_err!("expression did not evaluate to an array");
1168        };
1169        // keep `else_expr`'s data type and return type consistent
1170        let expr = try_cast(Arc::clone(e), &batch.schema(), return_type)?;
1171        let else_ = Scalar::new(expr.evaluate(batch)?.into_array(1)?);
1172        Ok(ColumnarValue::Array(zip(&when_value, &then_value, &else_)?))
1173    }
1174
1175    fn expr_or_expr(
1176        &self,
1177        batch: &RecordBatch,
1178        projected: &ProjectedCaseBody,
1179    ) -> Result<ColumnarValue> {
1180        // evaluate when condition on batch
1181        let when_value = self.body.when_then_expr[0].0.evaluate(batch)?;
1182        // `num_rows == 1` is intentional to avoid expanding scalars.
1183        // If the `when_value` is effectively a scalar, the 'all true' and 'all false' checks
1184        // below will avoid incorrectly using the scalar as a merge/zip mask.
1185        let when_value = when_value.into_array(1)?;
1186        let when_value = as_boolean_array(&when_value).map_err(|e| {
1187            DataFusionError::Context(
1188                "WHEN expression did not return a BooleanArray".to_string(),
1189                Box::new(e),
1190            )
1191        })?;
1192
1193        if when_value.null_count() == 0 && !when_value.has_false() {
1194            // All input rows are true, just call the 'then' expression
1195            self.body.when_then_expr[0].1.evaluate(batch)
1196        } else if !when_value.has_true() {
1197            // All input rows are false/null, just call the 'else' expression
1198            match &self.body.else_expr {
1199                Some(else_expr) => else_expr.evaluate(batch),
1200                None => {
1201                    let return_type = self.data_type(&batch.schema())?;
1202                    Ok(ColumnarValue::Scalar(ScalarValue::try_new_null(
1203                        &return_type,
1204                    )?))
1205                }
1206            }
1207        } else {
1208            // projected.projection may include indexes of lambda variables not available on this batch
1209            let projection = projected
1210                .projection
1211                .iter()
1212                .copied()
1213                .filter(|index| *index < batch.num_columns())
1214                .collect::<Vec<_>>();
1215            if projection.len() < batch.num_columns() {
1216                // The case expressions do not use all the columns of the input batch.
1217                // Project first to reduce time spent filtering.
1218                let projected_batch = batch.project(&projection)?;
1219                projected.body.expr_or_expr(&projected_batch, when_value)
1220            } else {
1221                // All columns are used in the case expressions, so there is no need to project.
1222                self.body.expr_or_expr(batch, when_value)
1223            }
1224        }
1225    }
1226
1227    fn with_lookup_table(
1228        &self,
1229        batch: &RecordBatch,
1230        lookup_table: &LiteralLookupTable,
1231    ) -> Result<ColumnarValue> {
1232        let expr = self.body.expr.as_ref().unwrap();
1233        let evaluated_expression = expr.evaluate(batch)?;
1234
1235        let is_scalar = matches!(evaluated_expression, ColumnarValue::Scalar(_));
1236        let evaluated_expression = evaluated_expression.to_array(1)?;
1237
1238        let values = lookup_table.map_keys_to_values(&evaluated_expression)?;
1239
1240        let result = if is_scalar {
1241            ColumnarValue::Scalar(ScalarValue::try_from_array(values.as_ref(), 0)?)
1242        } else {
1243            ColumnarValue::Array(values)
1244        };
1245
1246        Ok(result)
1247    }
1248}
1249
1250impl PhysicalExpr for CaseExpr {
1251    fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
1252        self.body.data_type(input_schema)
1253    }
1254
1255    fn nullable(&self, input_schema: &Schema) -> Result<bool> {
1256        let nullable_then = self
1257            .body
1258            .when_then_expr
1259            .iter()
1260            .filter_map(|(w, t)| {
1261                let is_nullable = match t.nullable(input_schema) {
1262                    // Pass on error determining nullability verbatim
1263                    Err(e) => return Some(Err(e)),
1264                    Ok(n) => n,
1265                };
1266
1267                // Branches with a then expression that is not nullable do not impact the
1268                // nullability of the case expression.
1269                if !is_nullable {
1270                    return None;
1271                }
1272
1273                // For case-with-expression assume all 'then' expressions are reachable
1274                if self.body.expr.is_some() {
1275                    return Some(Ok(()));
1276                }
1277
1278                // For branches with a nullable 'then' expression, try to determine
1279                // if the 'then' expression is ever reachable in the situation where
1280                // it would evaluate to null.
1281
1282                // Replace the `then` expression with `NULL` in the `when` expression
1283                let with_null = match replace_with_null(
1284                    w,
1285                    unwrap_certainly_null_expr(t.as_ref()),
1286                    input_schema,
1287                ) {
1288                    Err(e) => return Some(Err(e)),
1289                    Ok(e) => e,
1290                };
1291
1292                // Try to const evaluate the modified `when` expression.
1293                let predicate_result = match evaluate_predicate(&with_null) {
1294                    Err(e) => return Some(Err(e)),
1295                    Ok(b) => b,
1296                };
1297
1298                match predicate_result {
1299                    // Evaluation was inconclusive or true, so the 'then' expression is reachable
1300                    None | Some(true) => Some(Ok(())),
1301                    // Evaluation proves the branch will never be taken.
1302                    // The most common pattern for this is `WHEN x IS NOT NULL THEN x`.
1303                    Some(false) => None,
1304                }
1305            })
1306            .next();
1307
1308        if let Some(nullable_then) = nullable_then {
1309            // There is at least one reachable nullable 'then' expression, so the case
1310            // expression itself is nullable.
1311            // Use `Result::map` to propagate the error from `nullable_then` if there is one.
1312            nullable_then.map(|_| true)
1313        } else if let Some(e) = &self.body.else_expr {
1314            // There are no reachable nullable 'then' expressions, so all we still need to
1315            // check is the 'else' expression's nullability.
1316            e.nullable(input_schema)
1317        } else {
1318            // CASE produces NULL if there is no `else` expr
1319            // (aka when none of the `when_then_exprs` match)
1320            Ok(true)
1321        }
1322    }
1323
1324    fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
1325        match &self.eval_method {
1326            EvalMethod::WithExpression(p) => {
1327                // this use case evaluates "expr" and then compares the values with the "when"
1328                // values
1329                self.case_when_with_expr(batch, p)
1330            }
1331            EvalMethod::NoExpression(p) => {
1332                // The "when" conditions all evaluate to boolean in this use case and can be
1333                // arbitrary expressions
1334                self.case_when_no_expr(batch, p)
1335            }
1336            EvalMethod::InfallibleExprOrNull => {
1337                // Specialization for CASE WHEN expr THEN column [ELSE NULL] END
1338                self.case_column_or_null(batch)
1339            }
1340            EvalMethod::ScalarOrScalar => self.scalar_or_scalar(batch),
1341            EvalMethod::ExpressionOrExpression(p) => self.expr_or_expr(batch, p),
1342            EvalMethod::WithExprScalarLookupTable(lookup_table) => {
1343                self.with_lookup_table(batch, lookup_table)
1344            }
1345        }
1346    }
1347
1348    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
1349        let mut children = vec![];
1350        if let Some(expr) = &self.body.expr {
1351            children.push(expr)
1352        }
1353        self.body.when_then_expr.iter().for_each(|(cond, value)| {
1354            children.push(cond);
1355            children.push(value);
1356        });
1357
1358        if let Some(else_expr) = &self.body.else_expr {
1359            children.push(else_expr)
1360        }
1361        children
1362    }
1363
1364    // For physical CaseExpr, we do not allow modifying children size
1365    fn with_new_children(
1366        self: Arc<Self>,
1367        children: Vec<Arc<dyn PhysicalExpr>>,
1368    ) -> Result<Arc<dyn PhysicalExpr>> {
1369        if children.len() != self.children().len() {
1370            internal_err!("CaseExpr: Wrong number of children")
1371        } else {
1372            let (expr, when_then_expr, else_expr) =
1373                match (self.expr().is_some(), self.body.else_expr.is_some()) {
1374                    (true, true) => (
1375                        Some(&children[0]),
1376                        &children[1..children.len() - 1],
1377                        Some(&children[children.len() - 1]),
1378                    ),
1379                    (true, false) => {
1380                        (Some(&children[0]), &children[1..children.len()], None)
1381                    }
1382                    (false, true) => (
1383                        None,
1384                        &children[0..children.len() - 1],
1385                        Some(&children[children.len() - 1]),
1386                    ),
1387                    (false, false) => (None, &children[0..children.len()], None),
1388                };
1389            Ok(Arc::new(CaseExpr::try_new(
1390                expr.cloned(),
1391                when_then_expr.iter().cloned().tuples().collect(),
1392                else_expr.cloned(),
1393            )?))
1394        }
1395    }
1396
1397    fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1398        write!(f, "CASE ")?;
1399        if let Some(e) = &self.body.expr {
1400            e.fmt_sql(f)?;
1401            write!(f, " ")?;
1402        }
1403
1404        for (w, t) in &self.body.when_then_expr {
1405            write!(f, "WHEN ")?;
1406            w.fmt_sql(f)?;
1407            write!(f, " THEN ")?;
1408            t.fmt_sql(f)?;
1409            write!(f, " ")?;
1410        }
1411
1412        if let Some(e) = &self.body.else_expr {
1413            write!(f, "ELSE ")?;
1414            e.fmt_sql(f)?;
1415            write!(f, " ")?;
1416        }
1417        write!(f, "END")
1418    }
1419
1420    #[cfg(feature = "proto")]
1421    fn try_to_proto(
1422        &self,
1423        ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
1424    ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalExprNode>> {
1425        use datafusion_proto_models::protobuf;
1426
1427        Ok(Some(protobuf::PhysicalExprNode {
1428            expr_id: None,
1429            expr_type: Some(protobuf::physical_expr_node::ExprType::Case(Box::new(
1430                protobuf::PhysicalCaseNode {
1431                    expr: self
1432                        .expr()
1433                        .map(|expr| ctx.encode_child(expr).map(Box::new))
1434                        .transpose()?,
1435                    when_then_expr: self
1436                        .when_then_expr()
1437                        .iter()
1438                        .map(|(when_expr, then_expr)| {
1439                            Ok(protobuf::PhysicalWhenThen {
1440                                when_expr: Some(ctx.encode_child(when_expr)?),
1441                                then_expr: Some(ctx.encode_child(then_expr)?),
1442                            })
1443                        })
1444                        .collect::<Result<Vec<_>>>()?,
1445                    else_expr: self
1446                        .else_expr()
1447                        .map(|expr| ctx.encode_child(expr).map(Box::new))
1448                        .transpose()?,
1449                },
1450            ))),
1451        }))
1452    }
1453}
1454
1455#[cfg(feature = "proto")]
1456impl CaseExpr {
1457    /// Reconstruct a [`CaseExpr`] from its protobuf representation.
1458    pub fn try_from_proto(
1459        node: &datafusion_proto_models::protobuf::PhysicalExprNode,
1460        ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
1461    ) -> Result<Arc<dyn PhysicalExpr>> {
1462        use datafusion_physical_expr_common::expect_expr_variant;
1463        use datafusion_proto_models::protobuf;
1464
1465        let case = expect_expr_variant!(
1466            node,
1467            protobuf::physical_expr_node::ExprType::Case,
1468            "CaseExpr",
1469        );
1470
1471        Ok(Arc::new(CaseExpr::try_new(
1472            case.expr
1473                .as_deref()
1474                .map(|expr| ctx.decode(expr))
1475                .transpose()?,
1476            case.when_then_expr
1477                .iter()
1478                .map(|when_then| {
1479                    Ok((
1480                        ctx.decode_required_expression(
1481                            when_then.when_expr.as_ref(),
1482                            "CaseExpr",
1483                            "when_expr",
1484                        )?,
1485                        ctx.decode_required_expression(
1486                            when_then.then_expr.as_ref(),
1487                            "CaseExpr",
1488                            "then_expr",
1489                        )?,
1490                    ))
1491                })
1492                .collect::<Result<Vec<_>>>()?,
1493            case.else_expr
1494                .as_deref()
1495                .map(|expr| ctx.decode(expr))
1496                .transpose()?,
1497        )?))
1498    }
1499}
1500
1501/// Attempts to const evaluate the given `predicate`.
1502/// Returns:
1503/// - `Some(true)` if the predicate evaluates to a truthy value.
1504/// - `Some(false)` if the predicate evaluates to a falsy value.
1505/// - `None` if the predicate could not be evaluated.
1506fn evaluate_predicate(predicate: &Arc<dyn PhysicalExpr>) -> Result<Option<bool>> {
1507    // Create a dummy record with no columns and one row
1508    let batch = RecordBatch::try_new_with_options(
1509        Arc::new(Schema::empty()),
1510        vec![],
1511        &RecordBatchOptions::new().with_row_count(Some(1)),
1512    )?;
1513
1514    // Evaluate the predicate and interpret the result as a boolean
1515    let result = match predicate.evaluate(&batch) {
1516        // An error during evaluation means we couldn't const evaluate the predicate, so return `None`
1517        Err(_) => None,
1518        Ok(ColumnarValue::Array(array)) => Some(
1519            ScalarValue::try_from_array(array.as_ref(), 0)?
1520                .cast_to(&DataType::Boolean)?,
1521        ),
1522        Ok(ColumnarValue::Scalar(scalar)) => Some(scalar.cast_to(&DataType::Boolean)?),
1523    };
1524    Ok(result.map(|v| matches!(v, ScalarValue::Boolean(Some(true)))))
1525}
1526
1527fn replace_with_null(
1528    expr: &Arc<dyn PhysicalExpr>,
1529    expr_to_replace: &dyn PhysicalExpr,
1530    input_schema: &Schema,
1531) -> Result<Arc<dyn PhysicalExpr>, DataFusionError> {
1532    let with_null = Arc::clone(expr)
1533        .transform_down(|e| {
1534            if e.as_ref().dyn_eq(expr_to_replace) {
1535                let data_type = e.data_type(input_schema)?;
1536                let null_literal = lit(ScalarValue::try_new_null(&data_type)?);
1537                Ok(Transformed::yes(null_literal))
1538            } else {
1539                Ok(Transformed::no(e))
1540            }
1541        })?
1542        .data;
1543    Ok(with_null)
1544}
1545
1546/// Returns the innermost [`PhysicalExpr`] that is provably null if `expr` is null.
1547///
1548/// Keep this in sync with the logical-plan equivalent, `unwrap_certainly_null_expr`
1549/// in `datafusion/expr/src/expr_schema.rs`. If the two disagree on which wrappers
1550/// are null-preserving, `CASE` nullability computed by the logical and physical
1551/// planners can diverge and cause a schema mismatch during planning.
1552/// See <https://github.com/apache/datafusion/pull/23844> for rationale.
1553fn unwrap_certainly_null_expr(expr: &dyn PhysicalExpr) -> &dyn PhysicalExpr {
1554    if let Some(expr) = expr.downcast_ref::<NotExpr>() {
1555        unwrap_certainly_null_expr(expr.arg().as_ref())
1556    } else if let Some(expr) = expr.downcast_ref::<NegativeExpr>() {
1557        unwrap_certainly_null_expr(expr.arg().as_ref())
1558    } else if let Some(expr) = expr.downcast_ref::<CastExpr>() {
1559        unwrap_certainly_null_expr(expr.expr.as_ref())
1560    } else {
1561        expr
1562    }
1563}
1564
1565/// Create a CASE expression
1566pub fn case(
1567    expr: Option<Arc<dyn PhysicalExpr>>,
1568    when_thens: Vec<WhenThen>,
1569    else_expr: Option<Arc<dyn PhysicalExpr>>,
1570) -> Result<Arc<dyn PhysicalExpr>> {
1571    Ok(Arc::new(CaseExpr::try_new(expr, when_thens, else_expr)?))
1572}
1573
1574#[cfg(test)]
1575mod tests {
1576    use super::*;
1577
1578    use crate::expressions;
1579    use crate::expressions::{BinaryExpr, binary, cast, col, is_not_null};
1580    use arrow::buffer::Buffer;
1581    use arrow::datatypes::DataType::Float64;
1582    use arrow::datatypes::Field;
1583    use datafusion_common::cast::{as_float64_array, as_int32_array};
1584    use datafusion_common::plan_err;
1585    use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode};
1586    use datafusion_expr::type_coercion::binary::type_union_coercion;
1587    use datafusion_expr_common::operator::Operator;
1588    use datafusion_physical_expr_common::physical_expr::fmt_sql;
1589    use half::f16;
1590
1591    #[test]
1592    fn case_with_expr() -> Result<()> {
1593        let batch = case_test_batch()?;
1594        let schema = batch.schema();
1595
1596        // CASE a WHEN 'foo' THEN 123 WHEN 'bar' THEN 456 END
1597        let when1 = lit("foo");
1598        let then1 = lit(123i32);
1599        let when2 = lit("bar");
1600        let then2 = lit(456i32);
1601
1602        let expr = generate_case_when_with_type_coercion(
1603            Some(col("a", &schema)?),
1604            vec![(when1, then1), (when2, then2)],
1605            None,
1606            schema.as_ref(),
1607        )?;
1608        let result = expr
1609            .evaluate(&batch)?
1610            .into_array(batch.num_rows())
1611            .expect("Failed to convert to array");
1612        let result = as_int32_array(&result)?;
1613
1614        let expected = &Int32Array::from(vec![Some(123), None, None, Some(456)]);
1615
1616        assert_eq!(expected, result);
1617
1618        Ok(())
1619    }
1620
1621    #[test]
1622    fn case_with_expr_dictionary() -> Result<()> {
1623        let schema = Schema::new(vec![Field::new(
1624            "a",
1625            DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Utf8)),
1626            true,
1627        )]);
1628        let keys = UInt8Array::from(vec![0u8, 1u8, 2u8, 3u8]);
1629        let values = StringArray::from(vec![Some("foo"), Some("baz"), None, Some("bar")]);
1630        let dictionary = DictionaryArray::new(keys, Arc::new(values));
1631        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(dictionary)])?;
1632
1633        let schema = batch.schema();
1634
1635        // CASE a WHEN 'foo' THEN 123 WHEN 'bar' THEN 456 END
1636        let when1 = lit("foo");
1637        let then1 = lit(123i32);
1638        let when2 = lit("bar");
1639        let then2 = lit(456i32);
1640
1641        let expr = generate_case_when_with_type_coercion(
1642            Some(col("a", &schema)?),
1643            vec![(when1, then1), (when2, then2)],
1644            None,
1645            schema.as_ref(),
1646        )?;
1647        let result = expr
1648            .evaluate(&batch)?
1649            .into_array(batch.num_rows())
1650            .expect("Failed to convert to array");
1651        let result = as_int32_array(&result)?;
1652
1653        let expected = &Int32Array::from(vec![Some(123), None, None, Some(456)]);
1654
1655        assert_eq!(expected, result);
1656
1657        Ok(())
1658    }
1659
1660    // Make sure we are not failing when got literal in case when but input is dictionary encoded
1661    #[test]
1662    fn case_with_expr_primitive_dictionary() -> Result<()> {
1663        let schema = Schema::new(vec![Field::new(
1664            "a",
1665            DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::UInt64)),
1666            true,
1667        )]);
1668        let keys = UInt8Array::from(vec![0u8, 1u8, 2u8, 3u8]);
1669        let values = UInt64Array::from(vec![Some(10), Some(20), None, Some(30)]);
1670        let dictionary = DictionaryArray::new(keys, Arc::new(values));
1671        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(dictionary)])?;
1672
1673        let schema = batch.schema();
1674
1675        // CASE a WHEN 10 THEN 123 WHEN 30 THEN 456 END
1676        let when1 = lit(10_u64);
1677        let then1 = lit(123_i32);
1678        let when2 = lit(30_u64);
1679        let then2 = lit(456_i32);
1680
1681        let expr = generate_case_when_with_type_coercion(
1682            Some(col("a", &schema)?),
1683            vec![(when1, then1), (when2, then2)],
1684            None,
1685            schema.as_ref(),
1686        )?;
1687        let result = expr
1688            .evaluate(&batch)?
1689            .into_array(batch.num_rows())
1690            .expect("Failed to convert to array");
1691        let result = as_int32_array(&result)?;
1692
1693        let expected = &Int32Array::from(vec![Some(123), None, None, Some(456)]);
1694
1695        assert_eq!(expected, result);
1696
1697        Ok(())
1698    }
1699
1700    // Make sure we are not failing when got literal in case when but input is dictionary encoded
1701    #[test]
1702    fn case_with_expr_boolean_dictionary() -> Result<()> {
1703        let schema = Schema::new(vec![Field::new(
1704            "a",
1705            DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Boolean)),
1706            true,
1707        )]);
1708        let keys = UInt8Array::from(vec![0u8, 1u8, 2u8, 3u8]);
1709        let values = BooleanArray::from(vec![Some(true), Some(false), None, Some(true)]);
1710        let dictionary = DictionaryArray::new(keys, Arc::new(values));
1711        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(dictionary)])?;
1712
1713        let schema = batch.schema();
1714
1715        // CASE a WHEN true THEN 123 WHEN false THEN 456 END
1716        let when1 = lit(true);
1717        let then1 = lit(123i32);
1718        let when2 = lit(false);
1719        let then2 = lit(456i32);
1720
1721        let expr = generate_case_when_with_type_coercion(
1722            Some(col("a", &schema)?),
1723            vec![(when1, then1), (when2, then2)],
1724            None,
1725            schema.as_ref(),
1726        )?;
1727        let result = expr
1728            .evaluate(&batch)?
1729            .into_array(batch.num_rows())
1730            .expect("Failed to convert to array");
1731        let result = as_int32_array(&result)?;
1732
1733        let expected = &Int32Array::from(vec![Some(123), Some(456), None, Some(123)]);
1734
1735        assert_eq!(expected, result);
1736
1737        Ok(())
1738    }
1739
1740    #[test]
1741    fn case_with_expr_all_null_dictionary() -> Result<()> {
1742        let schema = Schema::new(vec![Field::new(
1743            "a",
1744            DataType::Dictionary(Box::new(DataType::UInt8), Box::new(DataType::Utf8)),
1745            true,
1746        )]);
1747        let keys = UInt8Array::from(vec![2u8, 2u8, 2u8, 2u8]);
1748        let values = StringArray::from(vec![Some("foo"), Some("baz"), None, Some("bar")]);
1749        let dictionary = DictionaryArray::new(keys, Arc::new(values));
1750        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(dictionary)])?;
1751
1752        let schema = batch.schema();
1753
1754        // CASE a WHEN 'foo' THEN 123 WHEN 'bar' THEN 456 END
1755        let when1 = lit("foo");
1756        let then1 = lit(123i32);
1757        let when2 = lit("bar");
1758        let then2 = lit(456i32);
1759
1760        let expr = generate_case_when_with_type_coercion(
1761            Some(col("a", &schema)?),
1762            vec![(when1, then1), (when2, then2)],
1763            None,
1764            schema.as_ref(),
1765        )?;
1766        let result = expr
1767            .evaluate(&batch)?
1768            .into_array(batch.num_rows())
1769            .expect("Failed to convert to array");
1770        let result = as_int32_array(&result)?;
1771
1772        let expected = &Int32Array::from(vec![None, None, None, None]);
1773
1774        assert_eq!(expected, result);
1775
1776        Ok(())
1777    }
1778
1779    #[test]
1780    fn case_with_expr_else() -> Result<()> {
1781        let batch = case_test_batch()?;
1782        let schema = batch.schema();
1783
1784        // CASE a WHEN 'foo' THEN 123 WHEN 'bar' THEN 456 ELSE 999 END
1785        let when1 = lit("foo");
1786        let then1 = lit(123i32);
1787        let when2 = lit("bar");
1788        let then2 = lit(456i32);
1789        let else_value = lit(999i32);
1790
1791        let expr = generate_case_when_with_type_coercion(
1792            Some(col("a", &schema)?),
1793            vec![(when1, then1), (when2, then2)],
1794            Some(else_value),
1795            schema.as_ref(),
1796        )?;
1797        let result = expr
1798            .evaluate(&batch)?
1799            .into_array(batch.num_rows())
1800            .expect("Failed to convert to array");
1801        let result = as_int32_array(&result)?;
1802
1803        let expected =
1804            &Int32Array::from(vec![Some(123), Some(999), Some(999), Some(456)]);
1805
1806        assert_eq!(expected, result);
1807
1808        Ok(())
1809    }
1810
1811    #[test]
1812    fn case_with_expr_divide_by_zero() -> Result<()> {
1813        let batch = case_test_batch1()?;
1814        let schema = batch.schema();
1815
1816        // CASE a when 0 THEN float64(null) ELSE 25.0 / cast(a, float64)  END
1817        let when1 = lit(0i32);
1818        let then1 = lit(ScalarValue::Float64(None));
1819        let else_value = binary(
1820            lit(25.0f64),
1821            Operator::Divide,
1822            cast(col("a", &schema)?, &batch.schema(), Float64)?,
1823            &batch.schema(),
1824        )?;
1825
1826        let expr = generate_case_when_with_type_coercion(
1827            Some(col("a", &schema)?),
1828            vec![(when1, then1)],
1829            Some(else_value),
1830            schema.as_ref(),
1831        )?;
1832        let result = expr
1833            .evaluate(&batch)?
1834            .into_array(batch.num_rows())
1835            .expect("Failed to convert to array");
1836        let result =
1837            as_float64_array(&result).expect("failed to downcast to Float64Array");
1838
1839        let expected = &Float64Array::from(vec![Some(25.0), None, None, Some(5.0)]);
1840
1841        assert_eq!(expected, result);
1842
1843        Ok(())
1844    }
1845
1846    #[test]
1847    fn case_without_expr() -> Result<()> {
1848        let batch = case_test_batch()?;
1849        let schema = batch.schema();
1850
1851        // CASE WHEN a = 'foo' THEN 123 WHEN a = 'bar' THEN 456 END
1852        let when1 = binary(
1853            col("a", &schema)?,
1854            Operator::Eq,
1855            lit("foo"),
1856            &batch.schema(),
1857        )?;
1858        let then1 = lit(123i32);
1859        let when2 = binary(
1860            col("a", &schema)?,
1861            Operator::Eq,
1862            lit("bar"),
1863            &batch.schema(),
1864        )?;
1865        let then2 = lit(456i32);
1866
1867        let expr = generate_case_when_with_type_coercion(
1868            None,
1869            vec![(when1, then1), (when2, then2)],
1870            None,
1871            schema.as_ref(),
1872        )?;
1873        let result = expr
1874            .evaluate(&batch)?
1875            .into_array(batch.num_rows())
1876            .expect("Failed to convert to array");
1877        let result = as_int32_array(&result)?;
1878
1879        let expected = &Int32Array::from(vec![Some(123), None, None, Some(456)]);
1880
1881        assert_eq!(expected, result);
1882
1883        Ok(())
1884    }
1885
1886    #[test]
1887    fn case_with_expr_when_null() -> Result<()> {
1888        let batch = case_test_batch()?;
1889        let schema = batch.schema();
1890
1891        // CASE a WHEN NULL THEN 0 WHEN a THEN 123 ELSE 999 END
1892        let when1 = lit(ScalarValue::Utf8(None));
1893        let then1 = lit(0i32);
1894        let when2 = col("a", &schema)?;
1895        let then2 = lit(123i32);
1896        let else_value = lit(999i32);
1897
1898        let expr = generate_case_when_with_type_coercion(
1899            Some(col("a", &schema)?),
1900            vec![(when1, then1), (when2, then2)],
1901            Some(else_value),
1902            schema.as_ref(),
1903        )?;
1904        let result = expr
1905            .evaluate(&batch)?
1906            .into_array(batch.num_rows())
1907            .expect("Failed to convert to array");
1908        let result = as_int32_array(&result)?;
1909
1910        let expected =
1911            &Int32Array::from(vec![Some(123), Some(123), Some(999), Some(123)]);
1912
1913        assert_eq!(expected, result);
1914
1915        Ok(())
1916    }
1917
1918    #[test]
1919    fn case_without_expr_divide_by_zero() -> Result<()> {
1920        let batch = case_test_batch1()?;
1921        let schema = batch.schema();
1922
1923        // CASE WHEN a > 0 THEN 25.0 / cast(a, float64) ELSE float64(null) END
1924        let when1 = binary(col("a", &schema)?, Operator::Gt, lit(0i32), &batch.schema())?;
1925        let then1 = binary(
1926            lit(25.0f64),
1927            Operator::Divide,
1928            cast(col("a", &schema)?, &batch.schema(), Float64)?,
1929            &batch.schema(),
1930        )?;
1931        let x = lit(ScalarValue::Float64(None));
1932
1933        let expr = generate_case_when_with_type_coercion(
1934            None,
1935            vec![(when1, then1)],
1936            Some(x),
1937            schema.as_ref(),
1938        )?;
1939        let result = expr
1940            .evaluate(&batch)?
1941            .into_array(batch.num_rows())
1942            .expect("Failed to convert to array");
1943        let result =
1944            as_float64_array(&result).expect("failed to downcast to Float64Array");
1945
1946        let expected = &Float64Array::from(vec![Some(25.0), None, None, Some(5.0)]);
1947
1948        assert_eq!(expected, result);
1949
1950        Ok(())
1951    }
1952
1953    fn case_test_batch1() -> Result<RecordBatch> {
1954        let schema = Schema::new(vec![
1955            Field::new("a", DataType::Int32, true),
1956            Field::new("b", DataType::Int32, true),
1957            Field::new("c", DataType::Int32, true),
1958        ]);
1959        let a = Int32Array::from(vec![Some(1), Some(0), None, Some(5)]);
1960        let b = Int32Array::from(vec![Some(3), None, Some(14), Some(7)]);
1961        let c = Int32Array::from(vec![Some(0), Some(-3), Some(777), None]);
1962        let batch = RecordBatch::try_new(
1963            Arc::new(schema),
1964            vec![Arc::new(a), Arc::new(b), Arc::new(c)],
1965        )?;
1966        Ok(batch)
1967    }
1968
1969    #[test]
1970    fn case_without_expr_else() -> Result<()> {
1971        let batch = case_test_batch()?;
1972        let schema = batch.schema();
1973
1974        // CASE WHEN a = 'foo' THEN 123 WHEN a = 'bar' THEN 456 ELSE 999 END
1975        let when1 = binary(
1976            col("a", &schema)?,
1977            Operator::Eq,
1978            lit("foo"),
1979            &batch.schema(),
1980        )?;
1981        let then1 = lit(123i32);
1982        let when2 = binary(
1983            col("a", &schema)?,
1984            Operator::Eq,
1985            lit("bar"),
1986            &batch.schema(),
1987        )?;
1988        let then2 = lit(456i32);
1989        let else_value = lit(999i32);
1990
1991        let expr = generate_case_when_with_type_coercion(
1992            None,
1993            vec![(when1, then1), (when2, then2)],
1994            Some(else_value),
1995            schema.as_ref(),
1996        )?;
1997        let result = expr
1998            .evaluate(&batch)?
1999            .into_array(batch.num_rows())
2000            .expect("Failed to convert to array");
2001        let result = as_int32_array(&result)?;
2002
2003        let expected =
2004            &Int32Array::from(vec![Some(123), Some(999), Some(999), Some(456)]);
2005
2006        assert_eq!(expected, result);
2007
2008        Ok(())
2009    }
2010
2011    #[test]
2012    fn case_with_type_cast() -> Result<()> {
2013        let batch = case_test_batch()?;
2014        let schema = batch.schema();
2015
2016        // CASE WHEN a = 'foo' THEN 123.3 ELSE 999 END
2017        let when = binary(
2018            col("a", &schema)?,
2019            Operator::Eq,
2020            lit("foo"),
2021            &batch.schema(),
2022        )?;
2023        let then = lit(123.3f64);
2024        let else_value = lit(999i32);
2025
2026        let expr = generate_case_when_with_type_coercion(
2027            None,
2028            vec![(when, then)],
2029            Some(else_value),
2030            schema.as_ref(),
2031        )?;
2032        let result = expr
2033            .evaluate(&batch)?
2034            .into_array(batch.num_rows())
2035            .expect("Failed to convert to array");
2036        let result =
2037            as_float64_array(&result).expect("failed to downcast to Float64Array");
2038
2039        let expected =
2040            &Float64Array::from(vec![Some(123.3), Some(999.0), Some(999.0), Some(999.0)]);
2041
2042        assert_eq!(expected, result);
2043
2044        Ok(())
2045    }
2046
2047    #[test]
2048    fn case_with_matches_and_nulls() -> Result<()> {
2049        let batch = case_test_batch_nulls()?;
2050        let schema = batch.schema();
2051
2052        // SELECT CASE WHEN load4 = 1.77 THEN load4 END
2053        let when = binary(
2054            col("load4", &schema)?,
2055            Operator::Eq,
2056            lit(1.77f64),
2057            &batch.schema(),
2058        )?;
2059        let then = col("load4", &schema)?;
2060
2061        let expr = generate_case_when_with_type_coercion(
2062            None,
2063            vec![(when, then)],
2064            None,
2065            schema.as_ref(),
2066        )?;
2067        let result = expr
2068            .evaluate(&batch)?
2069            .into_array(batch.num_rows())
2070            .expect("Failed to convert to array");
2071        let result =
2072            as_float64_array(&result).expect("failed to downcast to Float64Array");
2073
2074        let expected =
2075            &Float64Array::from(vec![Some(1.77), None, None, None, None, Some(1.77)]);
2076
2077        assert_eq!(expected, result);
2078
2079        Ok(())
2080    }
2081
2082    #[test]
2083    fn case_with_scalar_predicate() -> Result<()> {
2084        let batch = case_test_batch_nulls()?;
2085        let schema = batch.schema();
2086
2087        // SELECT CASE WHEN TRUE THEN load4 END
2088        let when = lit(true);
2089        let then = col("load4", &schema)?;
2090        let expr = generate_case_when_with_type_coercion(
2091            None,
2092            vec![(when, then)],
2093            None,
2094            schema.as_ref(),
2095        )?;
2096
2097        // many rows
2098        let result = expr
2099            .evaluate(&batch)?
2100            .into_array(batch.num_rows())
2101            .expect("Failed to convert to array");
2102        let result =
2103            as_float64_array(&result).expect("failed to downcast to Float64Array");
2104        let expected = &Float64Array::from(vec![
2105            Some(1.77),
2106            None,
2107            None,
2108            Some(1.78),
2109            None,
2110            Some(1.77),
2111        ]);
2112        assert_eq!(expected, result);
2113
2114        // one row
2115        let expected = Float64Array::from(vec![Some(1.1)]);
2116        let batch =
2117            RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(expected.clone())])?;
2118        let result = expr
2119            .evaluate(&batch)?
2120            .into_array(batch.num_rows())
2121            .expect("Failed to convert to array");
2122        let result =
2123            as_float64_array(&result).expect("failed to downcast to Float64Array");
2124        assert_eq!(&expected, result);
2125
2126        Ok(())
2127    }
2128
2129    #[test]
2130    fn case_expr_matches_and_nulls() -> Result<()> {
2131        let batch = case_test_batch_nulls()?;
2132        let schema = batch.schema();
2133
2134        // SELECT CASE load4 WHEN 1.77 THEN load4 END
2135        let expr = col("load4", &schema)?;
2136        let when = lit(1.77f64);
2137        let then = col("load4", &schema)?;
2138
2139        let expr = generate_case_when_with_type_coercion(
2140            Some(expr),
2141            vec![(when, then)],
2142            None,
2143            schema.as_ref(),
2144        )?;
2145        let result = expr
2146            .evaluate(&batch)?
2147            .into_array(batch.num_rows())
2148            .expect("Failed to convert to array");
2149        let result =
2150            as_float64_array(&result).expect("failed to downcast to Float64Array");
2151
2152        let expected =
2153            &Float64Array::from(vec![Some(1.77), None, None, None, None, Some(1.77)]);
2154
2155        assert_eq!(expected, result);
2156
2157        Ok(())
2158    }
2159
2160    #[test]
2161    fn test_when_null_and_some_cond_else_null() -> Result<()> {
2162        let batch = case_test_batch()?;
2163        let schema = batch.schema();
2164
2165        let when = binary(
2166            Arc::new(Literal::new(ScalarValue::Boolean(None))),
2167            Operator::And,
2168            binary(col("a", &schema)?, Operator::Eq, lit("foo"), &schema)?,
2169            &schema,
2170        )?;
2171        let then = col("a", &schema)?;
2172
2173        // SELECT CASE WHEN (NULL AND a = 'foo') THEN a ELSE NULL END
2174        let expr = Arc::new(CaseExpr::try_new(None, vec![(when, then)], None)?);
2175        let result = expr
2176            .evaluate(&batch)?
2177            .into_array(batch.num_rows())
2178            .expect("Failed to convert to array");
2179        let result = as_string_array(&result);
2180
2181        // all result values should be null
2182        assert_eq!(result.logical_null_count(), batch.num_rows());
2183        Ok(())
2184    }
2185
2186    fn case_test_batch() -> Result<RecordBatch> {
2187        let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
2188        let a = StringArray::from(vec![Some("foo"), Some("baz"), None, Some("bar")]);
2189        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)])?;
2190        Ok(batch)
2191    }
2192
2193    // Construct an array that has several NULL values whose
2194    // underlying buffer actually matches the where expr predicate
2195    fn case_test_batch_nulls() -> Result<RecordBatch> {
2196        let load4: Float64Array = vec![
2197            Some(1.77), // 1.77
2198            Some(1.77), // null <-- same value, but will be set to null
2199            Some(1.77), // null <-- same value, but will be set to null
2200            Some(1.78), // 1.78
2201            None,       // null
2202            Some(1.77), // 1.77
2203        ]
2204        .into_iter()
2205        .collect();
2206
2207        let null_buffer = Buffer::from([0b00101001u8]);
2208        let load4 = load4
2209            .into_data()
2210            .into_builder()
2211            .null_bit_buffer(Some(null_buffer))
2212            .build()
2213            .unwrap();
2214        let load4: Float64Array = load4.into();
2215
2216        let batch =
2217            RecordBatch::try_from_iter(vec![("load4", Arc::new(load4) as ArrayRef)])?;
2218        Ok(batch)
2219    }
2220
2221    #[test]
2222    fn case_test_incompatible() -> Result<()> {
2223        // 1 then is int64
2224        // 2 then is boolean
2225        let batch = case_test_batch()?;
2226        let schema = batch.schema();
2227
2228        // CASE WHEN a = 'foo' THEN 123 WHEN a = 'bar' THEN true END
2229        let when1 = binary(
2230            col("a", &schema)?,
2231            Operator::Eq,
2232            lit("foo"),
2233            &batch.schema(),
2234        )?;
2235        let then1 = lit(123i32);
2236        let when2 = binary(
2237            col("a", &schema)?,
2238            Operator::Eq,
2239            lit("bar"),
2240            &batch.schema(),
2241        )?;
2242        let then2 = lit(true);
2243
2244        let expr = generate_case_when_with_type_coercion(
2245            None,
2246            vec![(when1, then1), (when2, then2)],
2247            None,
2248            schema.as_ref(),
2249        );
2250        assert!(expr.is_err());
2251
2252        // then 1 is int32
2253        // then 2 is int64
2254        // else is float
2255        // CASE WHEN a = 'foo' THEN 123 WHEN a = 'bar' THEN 456 ELSE 1.23 END
2256        let when1 = binary(
2257            col("a", &schema)?,
2258            Operator::Eq,
2259            lit("foo"),
2260            &batch.schema(),
2261        )?;
2262        let then1 = lit(123i32);
2263        let when2 = binary(
2264            col("a", &schema)?,
2265            Operator::Eq,
2266            lit("bar"),
2267            &batch.schema(),
2268        )?;
2269        let then2 = lit(456i64);
2270        let else_expr = lit(1.23f64);
2271
2272        let expr = generate_case_when_with_type_coercion(
2273            None,
2274            vec![(when1, then1), (when2, then2)],
2275            Some(else_expr),
2276            schema.as_ref(),
2277        );
2278        assert!(expr.is_ok());
2279        let result_type = expr.unwrap().data_type(schema.as_ref())?;
2280        assert_eq!(Float64, result_type);
2281        Ok(())
2282    }
2283
2284    #[test]
2285    fn case_eq() -> Result<()> {
2286        let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
2287
2288        let when1 = lit("foo");
2289        let then1 = lit(123i32);
2290        let when2 = lit("bar");
2291        let then2 = lit(456i32);
2292        let else_value = lit(999i32);
2293
2294        let expr1 = generate_case_when_with_type_coercion(
2295            Some(col("a", &schema)?),
2296            vec![
2297                (Arc::clone(&when1), Arc::clone(&then1)),
2298                (Arc::clone(&when2), Arc::clone(&then2)),
2299            ],
2300            Some(Arc::clone(&else_value)),
2301            &schema,
2302        )?;
2303
2304        let expr2 = generate_case_when_with_type_coercion(
2305            Some(col("a", &schema)?),
2306            vec![
2307                (Arc::clone(&when1), Arc::clone(&then1)),
2308                (Arc::clone(&when2), Arc::clone(&then2)),
2309            ],
2310            Some(Arc::clone(&else_value)),
2311            &schema,
2312        )?;
2313
2314        let expr3 = generate_case_when_with_type_coercion(
2315            Some(col("a", &schema)?),
2316            vec![(Arc::clone(&when1), Arc::clone(&then1)), (when2, then2)],
2317            None,
2318            &schema,
2319        )?;
2320
2321        let expr4 = generate_case_when_with_type_coercion(
2322            Some(col("a", &schema)?),
2323            vec![(when1, then1)],
2324            Some(else_value),
2325            &schema,
2326        )?;
2327
2328        assert!(expr1.eq(&expr2));
2329        assert!(expr2.eq(&expr1));
2330
2331        assert!(expr2.ne(&expr3));
2332        assert!(expr3.ne(&expr2));
2333
2334        assert!(expr1.ne(&expr4));
2335        assert!(expr4.ne(&expr1));
2336
2337        Ok(())
2338    }
2339
2340    #[test]
2341    fn case_transform() -> Result<()> {
2342        let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
2343
2344        let when1 = lit("foo");
2345        let then1 = lit(123i32);
2346        let when2 = lit("bar");
2347        let then2 = lit(456i32);
2348        let else_value = lit(999i32);
2349
2350        let expr = generate_case_when_with_type_coercion(
2351            Some(col("a", &schema)?),
2352            vec![
2353                (Arc::clone(&when1), Arc::clone(&then1)),
2354                (Arc::clone(&when2), Arc::clone(&then2)),
2355            ],
2356            Some(Arc::clone(&else_value)),
2357            &schema,
2358        )?;
2359
2360        let expr2 = Arc::clone(&expr)
2361            .transform(|e| {
2362                let transformed = match e.downcast_ref::<Literal>() {
2363                    Some(lit_value) => match lit_value.value() {
2364                        ScalarValue::Utf8(Some(str_value)) => {
2365                            Some(lit(str_value.to_uppercase()))
2366                        }
2367                        _ => None,
2368                    },
2369                    _ => None,
2370                };
2371                Ok(if let Some(transformed) = transformed {
2372                    Transformed::yes(transformed)
2373                } else {
2374                    Transformed::no(e)
2375                })
2376            })
2377            .data()
2378            .unwrap();
2379
2380        let expr3 = Arc::clone(&expr)
2381            .transform_down(|e| {
2382                let transformed = match e.downcast_ref::<Literal>() {
2383                    Some(lit_value) => match lit_value.value() {
2384                        ScalarValue::Utf8(Some(str_value)) => {
2385                            Some(lit(str_value.to_uppercase()))
2386                        }
2387                        _ => None,
2388                    },
2389                    _ => None,
2390                };
2391                Ok(if let Some(transformed) = transformed {
2392                    Transformed::yes(transformed)
2393                } else {
2394                    Transformed::no(e)
2395                })
2396            })
2397            .data()
2398            .unwrap();
2399
2400        assert!(expr.ne(&expr2));
2401        assert!(expr2.eq(&expr3));
2402
2403        Ok(())
2404    }
2405
2406    #[test]
2407    fn test_column_or_null_specialization() -> Result<()> {
2408        // create input data
2409        let mut c1 = Int32Builder::new();
2410        let mut c2 = StringBuilder::new();
2411        for i in 0..1000 {
2412            c1.append_value(i);
2413            if i % 7 == 0 {
2414                c2.append_null();
2415            } else {
2416                c2.append_value(format!("string {i}"));
2417            }
2418        }
2419        let c1 = Arc::new(c1.finish());
2420        let c2 = Arc::new(c2.finish());
2421        let schema = Schema::new(vec![
2422            Field::new("c1", DataType::Int32, true),
2423            Field::new("c2", DataType::Utf8, true),
2424        ]);
2425        let batch = RecordBatch::try_new(Arc::new(schema), vec![c1, c2]).unwrap();
2426
2427        // CaseWhenExprOrNull should produce same results as CaseExpr
2428        let predicate = Arc::new(BinaryExpr::new(
2429            make_col("c1", 0),
2430            Operator::LtEq,
2431            make_lit_i32(250),
2432        ));
2433        let expr = CaseExpr::try_new(None, vec![(predicate, make_col("c2", 1))], None)?;
2434        assert_eq!(expr.eval_method, EvalMethod::InfallibleExprOrNull);
2435        match expr.evaluate(&batch)? {
2436            ColumnarValue::Array(array) => {
2437                assert_eq!(1000, array.len());
2438                assert_eq!(785, array.null_count());
2439            }
2440            _ => unreachable!(),
2441        }
2442        Ok(())
2443    }
2444
2445    #[test]
2446    fn test_expr_or_expr_specialization() -> Result<()> {
2447        let batch = case_test_batch1()?;
2448        let schema = batch.schema();
2449        let when = binary(
2450            col("a", &schema)?,
2451            Operator::LtEq,
2452            lit(2i32),
2453            &batch.schema(),
2454        )?;
2455        let then = col("b", &schema)?;
2456        let else_expr = col("c", &schema)?;
2457        let expr = CaseExpr::try_new(None, vec![(when, then)], Some(else_expr))?;
2458        assert!(matches!(
2459            expr.eval_method,
2460            EvalMethod::ExpressionOrExpression(_)
2461        ));
2462        let result = expr
2463            .evaluate(&batch)?
2464            .into_array(batch.num_rows())
2465            .expect("Failed to convert to array");
2466        let result = as_int32_array(&result).expect("failed to downcast to Int32Array");
2467
2468        let expected = &Int32Array::from(vec![Some(3), None, Some(777), None]);
2469
2470        assert_eq!(expected, result);
2471        Ok(())
2472    }
2473
2474    fn make_col(name: &str, index: usize) -> Arc<dyn PhysicalExpr> {
2475        Arc::new(Column::new(name, index))
2476    }
2477
2478    fn make_lit_i32(n: i32) -> Arc<dyn PhysicalExpr> {
2479        Arc::new(Literal::new(ScalarValue::Int32(Some(n))))
2480    }
2481
2482    fn generate_case_when_with_type_coercion(
2483        expr: Option<Arc<dyn PhysicalExpr>>,
2484        when_thens: Vec<WhenThen>,
2485        else_expr: Option<Arc<dyn PhysicalExpr>>,
2486        input_schema: &Schema,
2487    ) -> Result<Arc<dyn PhysicalExpr>> {
2488        let coerce_type =
2489            get_case_common_type(&when_thens, else_expr.clone(), input_schema);
2490        let (when_thens, else_expr) = match coerce_type {
2491            None => plan_err!(
2492                "Can't get a common type for then {when_thens:?} and else {else_expr:?} expression"
2493            ),
2494            Some(data_type) => {
2495                // cast then expr
2496                let left = when_thens
2497                    .into_iter()
2498                    .map(|(when, then)| {
2499                        let then = try_cast(then, input_schema, data_type.clone())?;
2500                        Ok((when, then))
2501                    })
2502                    .collect::<Result<Vec<_>>>()?;
2503                let right = match else_expr {
2504                    None => None,
2505                    Some(expr) => Some(try_cast(expr, input_schema, data_type.clone())?),
2506                };
2507
2508                Ok((left, right))
2509            }
2510        }?;
2511        case(expr, when_thens, else_expr)
2512    }
2513
2514    fn get_case_common_type(
2515        when_thens: &[WhenThen],
2516        else_expr: Option<Arc<dyn PhysicalExpr>>,
2517        input_schema: &Schema,
2518    ) -> Option<DataType> {
2519        let thens_type = when_thens
2520            .iter()
2521            .map(|when_then| {
2522                let data_type = &when_then.1.data_type(input_schema).unwrap();
2523                data_type.clone()
2524            })
2525            .collect::<Vec<_>>();
2526        let else_type = match else_expr {
2527            None => {
2528                // case when then exprs must have one then value
2529                thens_type[0].clone()
2530            }
2531            Some(else_phy_expr) => else_phy_expr.data_type(input_schema).unwrap(),
2532        };
2533        thens_type
2534            .iter()
2535            .try_fold(else_type, |left_type, right_type| {
2536                type_union_coercion(&left_type, right_type)
2537            })
2538    }
2539
2540    #[test]
2541    fn test_fmt_sql() -> Result<()> {
2542        let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
2543
2544        // CASE WHEN a = 'foo' THEN 123.3 ELSE 999 END
2545        let when = binary(col("a", &schema)?, Operator::Eq, lit("foo"), &schema)?;
2546        let then = lit(123.3f64);
2547        let else_value = lit(999i32);
2548
2549        let expr = generate_case_when_with_type_coercion(
2550            None,
2551            vec![(when, then)],
2552            Some(else_value),
2553            &schema,
2554        )?;
2555
2556        let display_string = expr.to_string();
2557        assert_eq!(
2558            display_string,
2559            "CASE WHEN a@0 = foo THEN 123.3 ELSE TRY_CAST(999 AS Float64) END"
2560        );
2561
2562        let sql_string = fmt_sql(expr.as_ref()).to_string();
2563        assert_eq!(
2564            sql_string,
2565            "CASE WHEN a = foo THEN 123.3 ELSE TRY_CAST(999 AS Float64) END"
2566        );
2567
2568        Ok(())
2569    }
2570
2571    fn when_then_else(
2572        when: &Arc<dyn PhysicalExpr>,
2573        then: &Arc<dyn PhysicalExpr>,
2574        els: &Arc<dyn PhysicalExpr>,
2575    ) -> Result<Arc<dyn PhysicalExpr>> {
2576        let case = CaseExpr::try_new(
2577            None,
2578            vec![(Arc::clone(when), Arc::clone(then))],
2579            Some(Arc::clone(els)),
2580        )?;
2581        Ok(Arc::new(case))
2582    }
2583
2584    #[test]
2585    fn test_case_expression_nullability_with_nullable_column() -> Result<()> {
2586        case_expression_nullability(true)
2587    }
2588
2589    #[test]
2590    fn test_case_expression_nullability_with_not_nullable_column() -> Result<()> {
2591        case_expression_nullability(false)
2592    }
2593
2594    fn case_expression_nullability(col_is_nullable: bool) -> Result<()> {
2595        let schema =
2596            Schema::new(vec![Field::new("foo", DataType::Int32, col_is_nullable)]);
2597
2598        let foo = col("foo", &schema)?;
2599        let foo_is_not_null = is_not_null(Arc::clone(&foo))?;
2600        let foo_is_null = expressions::is_null(Arc::clone(&foo))?;
2601        let not_foo_is_null = expressions::not(Arc::clone(&foo_is_null))?;
2602        let zero = lit(0);
2603        let foo_eq_zero =
2604            binary(Arc::clone(&foo), Operator::Eq, Arc::clone(&zero), &schema)?;
2605        let cast_foo = cast(Arc::clone(&foo), &schema, DataType::Int64)?;
2606        let negative_foo = expressions::negative(Arc::clone(&foo), &schema)?;
2607
2608        assert_not_nullable(when_then_else(&foo_is_not_null, &foo, &zero)?, &schema);
2609        assert_not_nullable(when_then_else(&not_foo_is_null, &foo, &zero)?, &schema);
2610        assert_not_nullable(when_then_else(&foo_eq_zero, &foo, &zero)?, &schema);
2611        assert_not_nullable(
2612            when_then_else(&foo_is_not_null, &cast_foo, &lit(0i64))?,
2613            &schema,
2614        );
2615        assert_not_nullable(
2616            when_then_else(&foo_is_not_null, &negative_foo, &zero)?,
2617            &schema,
2618        );
2619
2620        // Nested null-preserving wrappers must be unwrapped recursively. `CAST(-foo)`
2621        // still collapses `foo IS NOT NULL` to `false`, so the branch is
2622        // unreachable-as-null and the `CASE` is not nullable.
2623        let cast_negative_foo = cast(
2624            expressions::negative(Arc::clone(&foo), &schema)?,
2625            &schema,
2626            DataType::Int64,
2627        )?;
2628        assert_not_nullable(
2629            when_then_else(&foo_is_not_null, &cast_negative_foo, &lit(0i64))?,
2630            &schema,
2631        );
2632
2633        // `TRY_CAST` is intentionally NOT treated as null-preserving: it yields
2634        // NULL on a failed cast even for a non-null input, so a guarded `TRY_CAST`
2635        // branch is still reachable-as-null and the `CASE` stays nullable. This must
2636        // stay consistent with the logical planner (`unwrap_certainly_null_expr` in
2637        // `datafusion/expr/src/expr_schema.rs`); unwrapping it on only one side would
2638        // reintroduce a logical/physical schema mismatch.
2639        let try_cast_foo = try_cast(Arc::clone(&foo), &schema, DataType::Int64)?;
2640        assert_nullable(
2641            when_then_else(&foo_is_not_null, &try_cast_foo, &lit(0i64))?,
2642            &schema,
2643        );
2644
2645        assert_not_nullable(
2646            when_then_else(
2647                &binary(
2648                    Arc::clone(&foo_is_not_null),
2649                    Operator::And,
2650                    Arc::clone(&foo_eq_zero),
2651                    &schema,
2652                )?,
2653                &foo,
2654                &zero,
2655            )?,
2656            &schema,
2657        );
2658
2659        assert_not_nullable(
2660            when_then_else(
2661                &binary(
2662                    Arc::clone(&foo_eq_zero),
2663                    Operator::And,
2664                    Arc::clone(&foo_is_not_null),
2665                    &schema,
2666                )?,
2667                &foo,
2668                &zero,
2669            )?,
2670            &schema,
2671        );
2672
2673        assert_not_nullable(
2674            when_then_else(
2675                &binary(
2676                    Arc::clone(&foo_is_not_null),
2677                    Operator::Or,
2678                    Arc::clone(&foo_eq_zero),
2679                    &schema,
2680                )?,
2681                &foo,
2682                &zero,
2683            )?,
2684            &schema,
2685        );
2686
2687        assert_not_nullable(
2688            when_then_else(
2689                &binary(
2690                    Arc::clone(&foo_eq_zero),
2691                    Operator::Or,
2692                    Arc::clone(&foo_is_not_null),
2693                    &schema,
2694                )?,
2695                &foo,
2696                &zero,
2697            )?,
2698            &schema,
2699        );
2700
2701        assert_nullability(
2702            when_then_else(
2703                &binary(
2704                    Arc::clone(&foo_is_null),
2705                    Operator::Or,
2706                    Arc::clone(&foo_eq_zero),
2707                    &schema,
2708                )?,
2709                &foo,
2710                &zero,
2711            )?,
2712            &schema,
2713            col_is_nullable,
2714        );
2715
2716        assert_nullability(
2717            when_then_else(
2718                &binary(
2719                    binary(Arc::clone(&foo), Operator::Eq, Arc::clone(&zero), &schema)?,
2720                    Operator::Or,
2721                    Arc::clone(&foo_is_null),
2722                    &schema,
2723                )?,
2724                &foo,
2725                &zero,
2726            )?,
2727            &schema,
2728            col_is_nullable,
2729        );
2730
2731        assert_not_nullable(
2732            when_then_else(
2733                &binary(
2734                    binary(
2735                        binary(
2736                            Arc::clone(&foo),
2737                            Operator::Eq,
2738                            Arc::clone(&zero),
2739                            &schema,
2740                        )?,
2741                        Operator::And,
2742                        Arc::clone(&foo_is_not_null),
2743                        &schema,
2744                    )?,
2745                    Operator::Or,
2746                    binary(
2747                        binary(
2748                            Arc::clone(&foo),
2749                            Operator::Eq,
2750                            Arc::clone(&foo),
2751                            &schema,
2752                        )?,
2753                        Operator::And,
2754                        Arc::clone(&foo_is_not_null),
2755                        &schema,
2756                    )?,
2757                    &schema,
2758                )?,
2759                &foo,
2760                &zero,
2761            )?,
2762            &schema,
2763        );
2764
2765        let boolean_schema =
2766            Schema::new(vec![Field::new("predicate", DataType::Boolean, true)]);
2767        let predicate = col("predicate", &boolean_schema)?;
2768        let predicate_is_not_null = is_not_null(Arc::clone(&predicate))?;
2769        let not_predicate = expressions::not(Arc::clone(&predicate))?;
2770        assert_not_nullable(
2771            when_then_else(&predicate_is_not_null, &not_predicate, &lit(false))?,
2772            &boolean_schema,
2773        );
2774
2775        // Nested `NOT` is likewise unwrapped recursively.
2776        let not_not_predicate = expressions::not(Arc::clone(&not_predicate))?;
2777        assert_not_nullable(
2778            when_then_else(&predicate_is_not_null, &not_not_predicate, &lit(false))?,
2779            &boolean_schema,
2780        );
2781
2782        Ok(())
2783    }
2784
2785    fn assert_not_nullable(expr: Arc<dyn PhysicalExpr>, schema: &Schema) {
2786        assert!(!expr.nullable(schema).unwrap());
2787    }
2788
2789    fn assert_nullable(expr: Arc<dyn PhysicalExpr>, schema: &Schema) {
2790        assert!(expr.nullable(schema).unwrap());
2791    }
2792
2793    fn assert_nullability(expr: Arc<dyn PhysicalExpr>, schema: &Schema, nullable: bool) {
2794        if nullable {
2795            assert_nullable(expr, schema);
2796        } else {
2797            assert_not_nullable(expr, schema);
2798        }
2799    }
2800
2801    // Test Lookup evaluation
2802
2803    fn test_case_when_literal_lookup(
2804        values: ArrayRef,
2805        lookup_map: &[(ScalarValue, ScalarValue)],
2806        else_value: Option<ScalarValue>,
2807        expected: ArrayRef,
2808    ) {
2809        // Create lookup
2810        // CASE <expr>
2811        // WHEN <when_constant_1> THEN <then_constant_1>
2812        // WHEN <when_constant_2> THEN <then_constant_2>
2813        // [ ELSE <else_constant> ]
2814
2815        let schema = Schema::new(vec![Field::new(
2816            "a",
2817            values.data_type().clone(),
2818            values.is_nullable(),
2819        )]);
2820        let schema = Arc::new(schema);
2821
2822        let batch = RecordBatch::try_new(schema, vec![values])
2823            .expect("failed to create RecordBatch");
2824
2825        let schema = batch.schema_ref();
2826        let case = col("a", schema).expect("failed to create col");
2827
2828        let when_then = lookup_map
2829            .iter()
2830            .map(|(when, then)| {
2831                (
2832                    Arc::new(Literal::new(when.clone())) as _,
2833                    Arc::new(Literal::new(then.clone())) as _,
2834                )
2835            })
2836            .collect::<Vec<WhenThen>>();
2837
2838        let else_expr = else_value.map(|else_value| {
2839            Arc::new(Literal::new(else_value)) as Arc<dyn PhysicalExpr>
2840        });
2841        let expr = CaseExpr::try_new(Some(case), when_then, else_expr)
2842            .expect("failed to create case");
2843
2844        // Assert that we are testing what we intend to assert
2845        assert!(
2846            matches!(
2847                expr.eval_method,
2848                EvalMethod::WithExprScalarLookupTable { .. }
2849            ),
2850            "we should use the expected eval method"
2851        );
2852
2853        let actual = expr
2854            .evaluate(&batch)
2855            .expect("failed to evaluate case")
2856            .into_array(batch.num_rows())
2857            .expect("Failed to convert to array");
2858
2859        assert_eq!(
2860            actual.data_type(),
2861            expected.data_type(),
2862            "Data type mismatch"
2863        );
2864
2865        assert_eq!(
2866            actual.as_ref(),
2867            expected.as_ref(),
2868            "actual (left) does not match expected (right)"
2869        );
2870    }
2871
2872    fn create_lookup<When, Then>(
2873        when_then_pairs: impl IntoIterator<Item = (When, Then)>,
2874    ) -> Vec<(ScalarValue, ScalarValue)>
2875    where
2876        ScalarValue: From<When>,
2877        ScalarValue: From<Then>,
2878    {
2879        when_then_pairs
2880            .into_iter()
2881            .map(|(when, then)| (ScalarValue::from(when), ScalarValue::from(then)))
2882            .collect()
2883    }
2884
2885    fn create_input_and_expected<Input, Expected, InputFromItem, ExpectedFromItem>(
2886        input_and_expected_pairs: impl IntoIterator<Item = (InputFromItem, ExpectedFromItem)>,
2887    ) -> (Input, Expected)
2888    where
2889        Input: Array + From<Vec<InputFromItem>>,
2890        Expected: Array + From<Vec<ExpectedFromItem>>,
2891    {
2892        let (input_items, expected_items): (Vec<InputFromItem>, Vec<ExpectedFromItem>) =
2893            input_and_expected_pairs.into_iter().unzip();
2894
2895        (Input::from(input_items), Expected::from(expected_items))
2896    }
2897
2898    fn test_lookup_eval_with_and_without_else(
2899        lookup_map: &[(ScalarValue, ScalarValue)],
2900        input_values: ArrayRef,
2901        expected: StringArray,
2902    ) {
2903        // Testing without ELSE should fallback to None
2904        test_case_when_literal_lookup(
2905            Arc::clone(&input_values),
2906            lookup_map,
2907            None,
2908            Arc::new(expected.clone()),
2909        );
2910
2911        // Testing with Else
2912        let else_value = "___fallback___";
2913
2914        // Changing each expected None to be fallback
2915        let expected_with_else = expected
2916            .iter()
2917            .map(|item| item.unwrap_or(else_value))
2918            .map(Some)
2919            .collect::<StringArray>();
2920
2921        // Test case
2922        test_case_when_literal_lookup(
2923            input_values,
2924            lookup_map,
2925            Some(ScalarValue::Utf8(Some(else_value.to_string()))),
2926            Arc::new(expected_with_else),
2927        );
2928    }
2929
2930    #[test]
2931    fn test_case_when_literal_lookup_int32_to_string() {
2932        let lookup_map = create_lookup([
2933            (Some(4), Some("four")),
2934            (Some(2), Some("two")),
2935            (Some(3), Some("three")),
2936            (Some(1), Some("one")),
2937        ]);
2938
2939        let (input_values, expected) =
2940            create_input_and_expected::<Int32Array, StringArray, _, _>([
2941                (1, Some("one")),
2942                (2, Some("two")),
2943                (3, Some("three")),
2944                (3, Some("three")),
2945                (2, Some("two")),
2946                (3, Some("three")),
2947                (5, None), // No match in WHEN
2948                (5, None), // No match in WHEN
2949                (3, Some("three")),
2950                (5, None), // No match in WHEN
2951            ]);
2952
2953        test_lookup_eval_with_and_without_else(
2954            &lookup_map,
2955            Arc::new(input_values),
2956            expected,
2957        );
2958    }
2959
2960    #[test]
2961    fn test_case_when_literal_lookup_none_case_should_never_match() {
2962        let lookup_map = create_lookup([
2963            (Some(4), Some("four")),
2964            (None, Some("none")),
2965            (Some(2), Some("two")),
2966            (Some(1), Some("one")),
2967        ]);
2968
2969        let (input_values, expected) =
2970            create_input_and_expected::<Int32Array, StringArray, _, _>([
2971                (Some(1), Some("one")),
2972                (Some(5), None), // No match in WHEN
2973                (None, None), // None cases are never match in CASE <expr> WHEN <value> syntax
2974                (Some(2), Some("two")),
2975                (None, None), // None cases are never match in CASE <expr> WHEN <value> syntax
2976                (None, None), // None cases are never match in CASE <expr> WHEN <value> syntax
2977                (Some(2), Some("two")),
2978                (Some(5), None), // No match in WHEN
2979            ]);
2980
2981        test_lookup_eval_with_and_without_else(
2982            &lookup_map,
2983            Arc::new(input_values),
2984            expected,
2985        );
2986    }
2987
2988    #[test]
2989    fn test_case_when_literal_lookup_int32_to_string_with_duplicate_cases() {
2990        let lookup_map = create_lookup([
2991            (Some(4), Some("four")),
2992            (Some(4), Some("no 4")),
2993            (Some(2), Some("two")),
2994            (Some(2), Some("no 2")),
2995            (Some(3), Some("three")),
2996            (Some(3), Some("no 3")),
2997            (Some(2), Some("no 2")),
2998            (Some(4), Some("no 4")),
2999            (Some(2), Some("no 2")),
3000            (Some(3), Some("no 3")),
3001            (Some(4), Some("no 4")),
3002            (Some(2), Some("no 2")),
3003            (Some(3), Some("no 3")),
3004            (Some(3), Some("no 3")),
3005        ]);
3006
3007        let (input_values, expected) =
3008            create_input_and_expected::<Int32Array, StringArray, _, _>([
3009                (1, None), // No match in WHEN
3010                (2, Some("two")),
3011                (3, Some("three")),
3012                (3, Some("three")),
3013                (2, Some("two")),
3014                (3, Some("three")),
3015                (5, None), // No match in WHEN
3016                (5, None), // No match in WHEN
3017                (3, Some("three")),
3018                (5, None), // No match in WHEN
3019            ]);
3020
3021        test_lookup_eval_with_and_without_else(
3022            &lookup_map,
3023            Arc::new(input_values),
3024            expected,
3025        );
3026    }
3027
3028    #[test]
3029    fn test_case_when_literal_lookup_f32_to_string_with_special_values_and_duplicate_cases()
3030     {
3031        let lookup_map = create_lookup([
3032            (Some(4.0), Some("four point zero")),
3033            (Some(f32::NAN), Some("NaN")),
3034            (Some(3.2), Some("three point two")),
3035            // Duplicate case to make sure it is not used
3036            (Some(f32::NAN), Some("should not use this NaN branch")),
3037            (Some(f32::INFINITY), Some("Infinity")),
3038            (Some(0.0), Some("zero")),
3039            // Duplicate case to make sure it is not used
3040            (
3041                Some(f32::INFINITY),
3042                Some("should not use this Infinity branch"),
3043            ),
3044            (Some(1.1), Some("one point one")),
3045        ]);
3046
3047        let (input_values, expected) =
3048            create_input_and_expected::<Float32Array, StringArray, _, _>([
3049                (1.1, Some("one point one")),
3050                (f32::NAN, Some("NaN")),
3051                (3.2, Some("three point two")),
3052                (3.2, Some("three point two")),
3053                (0.0, Some("zero")),
3054                (f32::INFINITY, Some("Infinity")),
3055                (3.2, Some("three point two")),
3056                (f32::NEG_INFINITY, None), // No match in WHEN
3057                (f32::NEG_INFINITY, None), // No match in WHEN
3058                (3.2, Some("three point two")),
3059                (-0.0, None), // No match in WHEN
3060            ]);
3061
3062        test_lookup_eval_with_and_without_else(
3063            &lookup_map,
3064            Arc::new(input_values),
3065            expected,
3066        );
3067    }
3068
3069    #[test]
3070    fn test_case_when_literal_lookup_f16_to_string_with_special_values() {
3071        let lookup_map = create_lookup([
3072            (
3073                ScalarValue::Float16(Some(f16::from_f32(3.2))),
3074                Some("3 dot 2"),
3075            ),
3076            (ScalarValue::Float16(Some(f16::NAN)), Some("NaN")),
3077            (
3078                ScalarValue::Float16(Some(f16::from_f32(17.4))),
3079                Some("17 dot 4"),
3080            ),
3081            (ScalarValue::Float16(Some(f16::INFINITY)), Some("Infinity")),
3082            (ScalarValue::Float16(Some(f16::ZERO)), Some("zero")),
3083        ]);
3084
3085        let (input_values, expected) =
3086            create_input_and_expected::<Float16Array, StringArray, _, _>([
3087                (f16::from_f32(3.2), Some("3 dot 2")),
3088                (f16::NAN, Some("NaN")),
3089                (f16::from_f32(17.4), Some("17 dot 4")),
3090                (f16::from_f32(17.4), Some("17 dot 4")),
3091                (f16::INFINITY, Some("Infinity")),
3092                (f16::from_f32(17.4), Some("17 dot 4")),
3093                (f16::NEG_INFINITY, None), // No match in WHEN
3094                (f16::NEG_INFINITY, None), // No match in WHEN
3095                (f16::from_f32(17.4), Some("17 dot 4")),
3096                (f16::NEG_ZERO, None), // No match in WHEN
3097            ]);
3098
3099        test_lookup_eval_with_and_without_else(
3100            &lookup_map,
3101            Arc::new(input_values),
3102            expected,
3103        );
3104    }
3105
3106    #[test]
3107    fn test_case_when_literal_lookup_f32_to_string_with_special_values() {
3108        let lookup_map = create_lookup([
3109            (3.2, Some("3 dot 2")),
3110            (f32::NAN, Some("NaN")),
3111            (17.4, Some("17 dot 4")),
3112            (f32::INFINITY, Some("Infinity")),
3113            (f32::ZERO, Some("zero")),
3114        ]);
3115
3116        let (input_values, expected) =
3117            create_input_and_expected::<Float32Array, StringArray, _, _>([
3118                (3.2, Some("3 dot 2")),
3119                (f32::NAN, Some("NaN")),
3120                (17.4, Some("17 dot 4")),
3121                (17.4, Some("17 dot 4")),
3122                (f32::INFINITY, Some("Infinity")),
3123                (17.4, Some("17 dot 4")),
3124                (f32::NEG_INFINITY, None), // No match in WHEN
3125                (f32::NEG_INFINITY, None), // No match in WHEN
3126                (17.4, Some("17 dot 4")),
3127                (-0.0, None), // No match in WHEN
3128            ]);
3129
3130        test_lookup_eval_with_and_without_else(
3131            &lookup_map,
3132            Arc::new(input_values),
3133            expected,
3134        );
3135    }
3136
3137    #[test]
3138    fn test_case_when_literal_lookup_f64_to_string_with_special_values() {
3139        let lookup_map = create_lookup([
3140            (3.2, Some("3 dot 2")),
3141            (f64::NAN, Some("NaN")),
3142            (17.4, Some("17 dot 4")),
3143            (f64::INFINITY, Some("Infinity")),
3144            (f64::ZERO, Some("zero")),
3145        ]);
3146
3147        let (input_values, expected) =
3148            create_input_and_expected::<Float64Array, StringArray, _, _>([
3149                (3.2, Some("3 dot 2")),
3150                (f64::NAN, Some("NaN")),
3151                (17.4, Some("17 dot 4")),
3152                (17.4, Some("17 dot 4")),
3153                (f64::INFINITY, Some("Infinity")),
3154                (17.4, Some("17 dot 4")),
3155                (f64::NEG_INFINITY, None), // No match in WHEN
3156                (f64::NEG_INFINITY, None), // No match in WHEN
3157                (17.4, Some("17 dot 4")),
3158                (-0.0, None), // No match in WHEN
3159            ]);
3160
3161        test_lookup_eval_with_and_without_else(
3162            &lookup_map,
3163            Arc::new(input_values),
3164            expected,
3165        );
3166    }
3167
3168    // Test that we don't lose the decimal precision and scale info
3169    #[test]
3170    fn test_decimal_with_non_default_precision_and_scale() {
3171        let lookup_map = create_lookup([
3172            (ScalarValue::Decimal32(Some(4), 3, 2), Some("four")),
3173            (ScalarValue::Decimal32(Some(2), 3, 2), Some("two")),
3174            (ScalarValue::Decimal32(Some(3), 3, 2), Some("three")),
3175            (ScalarValue::Decimal32(Some(1), 3, 2), Some("one")),
3176        ]);
3177
3178        let (input_values, expected) =
3179            create_input_and_expected::<Decimal32Array, StringArray, _, _>([
3180                (1, Some("one")),
3181                (2, Some("two")),
3182                (3, Some("three")),
3183                (3, Some("three")),
3184                (2, Some("two")),
3185                (3, Some("three")),
3186                (5, None), // No match in WHEN
3187                (5, None), // No match in WHEN
3188                (3, Some("three")),
3189                (5, None), // No match in WHEN
3190            ]);
3191
3192        let input_values = input_values
3193            .with_precision_and_scale(3, 2)
3194            .expect("must be able to set precision and scale");
3195
3196        test_lookup_eval_with_and_without_else(
3197            &lookup_map,
3198            Arc::new(input_values),
3199            expected,
3200        );
3201    }
3202
3203    // Test that we don't lose the timezone info
3204    #[test]
3205    fn test_timestamp_with_non_default_timezone() {
3206        let timezone: Option<Arc<str>> = Some("-10:00".into());
3207        let lookup_map = create_lookup([
3208            (
3209                ScalarValue::TimestampMillisecond(Some(4), timezone.clone()),
3210                Some("four"),
3211            ),
3212            (
3213                ScalarValue::TimestampMillisecond(Some(2), timezone.clone()),
3214                Some("two"),
3215            ),
3216            (
3217                ScalarValue::TimestampMillisecond(Some(3), timezone.clone()),
3218                Some("three"),
3219            ),
3220            (
3221                ScalarValue::TimestampMillisecond(Some(1), timezone.clone()),
3222                Some("one"),
3223            ),
3224        ]);
3225
3226        let (input_values, expected) =
3227            create_input_and_expected::<TimestampMillisecondArray, StringArray, _, _>([
3228                (1, Some("one")),
3229                (2, Some("two")),
3230                (3, Some("three")),
3231                (3, Some("three")),
3232                (2, Some("two")),
3233                (3, Some("three")),
3234                (5, None), // No match in WHEN
3235                (5, None), // No match in WHEN
3236                (3, Some("three")),
3237                (5, None), // No match in WHEN
3238            ]);
3239
3240        let input_values = input_values.with_timezone_opt(timezone);
3241
3242        test_lookup_eval_with_and_without_else(
3243            &lookup_map,
3244            Arc::new(input_values),
3245            expected,
3246        );
3247    }
3248
3249    #[test]
3250    fn test_with_strings_to_int32() {
3251        let lookup_map = create_lookup([
3252            (Some("why"), Some(42)),
3253            (Some("what"), Some(22)),
3254            (Some("when"), Some(17)),
3255        ]);
3256
3257        let (input_values, expected) =
3258            create_input_and_expected::<StringArray, Int32Array, _, _>([
3259                (Some("why"), Some(42)),
3260                (Some("5"), None), // No match in WHEN
3261                (None, None), // None cases are never match in CASE <expr> WHEN <value> syntax
3262                (Some("what"), Some(22)),
3263                (None, None), // None cases are never match in CASE <expr> WHEN <value> syntax
3264                (None, None), // None cases are never match in CASE <expr> WHEN <value> syntax
3265                (Some("what"), Some(22)),
3266                (Some("5"), None), // No match in WHEN
3267            ]);
3268
3269        let input_values = Arc::new(input_values) as ArrayRef;
3270
3271        // Testing without ELSE should fallback to None
3272        test_case_when_literal_lookup(
3273            Arc::clone(&input_values),
3274            &lookup_map,
3275            None,
3276            Arc::new(expected.clone()),
3277        );
3278
3279        // Testing with Else
3280        let else_value = 101;
3281
3282        // Changing each expected None to be fallback
3283        let expected_with_else = expected
3284            .iter()
3285            .map(|item| item.unwrap_or(else_value))
3286            .map(Some)
3287            .collect::<Int32Array>();
3288
3289        // Test case
3290        test_case_when_literal_lookup(
3291            input_values,
3292            &lookup_map,
3293            Some(ScalarValue::Int32(Some(else_value))),
3294            Arc::new(expected_with_else),
3295        );
3296    }
3297
3298    /// Reproduces https://github.com/apache/datafusion/issues/22173
3299    ///
3300    /// Nested self-referential CASE chains (common in rewrite-style projections)
3301    /// should not cause exponential hashing work during physical planning.
3302    #[test]
3303    fn nested_self_referential_case_hash_stays_bounded() -> Result<()> {
3304        use std::hash::Hasher;
3305
3306        #[derive(Default)]
3307        struct CountingHasher {
3308            write_calls: usize,
3309            bytes_written: usize,
3310        }
3311
3312        impl Hasher for CountingHasher {
3313            fn finish(&self) -> u64 {
3314                0
3315            }
3316
3317            fn write(&mut self, bytes: &[u8]) {
3318                self.write_calls += 1;
3319                self.bytes_written += bytes.len();
3320            }
3321        }
3322
3323        let schema =
3324            Arc::new(Schema::new(vec![Field::new("kind", DataType::Utf8, true)]));
3325
3326        let kind = col("kind", &schema)?;
3327        let mut label = Arc::clone(&kind);
3328
3329        let num_levels = 18;
3330        for idx in 0..num_levels {
3331            let predicate = Arc::new(BinaryExpr::new(
3332                Arc::clone(&kind),
3333                Operator::Eq,
3334                lit(idx.to_string()),
3335            )) as Arc<dyn PhysicalExpr>;
3336
3337            label = case(None, vec![(predicate, lit("label"))], Some(label))?;
3338        }
3339
3340        let mut hasher = CountingHasher::default();
3341        label.hash(&mut hasher);
3342
3343        assert!(
3344            hasher.write_calls < 50_000,
3345            "hashing nested CASE expression took {} hasher writes and {} bytes",
3346            hasher.write_calls,
3347            hasher.bytes_written
3348        );
3349
3350        Ok(())
3351    }
3352}
3353
3354#[cfg(all(test, feature = "proto"))]
3355mod proto_tests {
3356    use super::*;
3357    use crate::expressions::col;
3358    use crate::proto_test_util::{
3359        StubDecoder, StubEncoder, UnreachableDecoder, column_node,
3360    };
3361    use arrow::datatypes::Field;
3362    use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
3363    use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
3364    use datafusion_proto_models::protobuf;
3365    use datafusion_proto_models::protobuf::{PhysicalExprNode, PhysicalWhenThen};
3366
3367    fn proto_case_fixture() -> CaseExpr {
3368        let schema = Schema::new(vec![Field::new("a", DataType::Boolean, true)]);
3369        CaseExpr::try_new(
3370            Some(col("a", &schema).unwrap()),
3371            vec![(lit(true), lit(1_i32))],
3372            Some(lit(0_i32)),
3373        )
3374        .unwrap()
3375    }
3376
3377    fn proto_when_then(
3378        when_expr: Option<PhysicalExprNode>,
3379        then_expr: Option<PhysicalExprNode>,
3380    ) -> PhysicalWhenThen {
3381        PhysicalWhenThen {
3382            when_expr,
3383            then_expr,
3384        }
3385    }
3386
3387    fn proto_case_node(
3388        expr: Option<Box<PhysicalExprNode>>,
3389        when_then_expr: Vec<PhysicalWhenThen>,
3390        else_expr: Option<Box<PhysicalExprNode>>,
3391    ) -> PhysicalExprNode {
3392        PhysicalExprNode {
3393            expr_id: None,
3394            expr_type: Some(protobuf::physical_expr_node::ExprType::Case(Box::new(
3395                protobuf::PhysicalCaseNode {
3396                    expr,
3397                    when_then_expr,
3398                    else_expr,
3399                },
3400            ))),
3401        }
3402    }
3403
3404    #[test]
3405    fn try_to_proto_encodes_case_expr() {
3406        let case = proto_case_fixture();
3407        let encoder = StubEncoder::ok();
3408        let ctx = PhysicalExprEncodeCtx::new(&encoder);
3409
3410        let node = case
3411            .try_to_proto(&ctx)
3412            .unwrap()
3413            .expect("CaseExpr should encode to Some(node)");
3414
3415        assert!(node.expr_id.is_none());
3416        let case_node = match node.expr_type {
3417            Some(protobuf::physical_expr_node::ExprType::Case(boxed)) => *boxed,
3418            other => panic!("expected a CaseExpr node, got {other:?}"),
3419        };
3420        assert!(case_node.expr.is_some());
3421        assert_eq!(case_node.when_then_expr.len(), 1);
3422        assert!(case_node.when_then_expr[0].when_expr.is_some());
3423        assert!(case_node.when_then_expr[0].then_expr.is_some());
3424        assert!(case_node.else_expr.is_some());
3425    }
3426
3427    #[test]
3428    fn try_to_proto_propagates_child_encode_error() {
3429        let case = proto_case_fixture();
3430        // Call 1 is the optional CASE expr, call 2 is the WHEN expr.
3431        let encoder = StubEncoder::failing_on(2);
3432        let ctx = PhysicalExprEncodeCtx::new(&encoder);
3433
3434        let err = case.try_to_proto(&ctx).unwrap_err();
3435        assert!(matches!(err, DataFusionError::Internal(msg) if msg.contains("call 2")));
3436    }
3437
3438    #[test]
3439    fn try_from_proto_decodes_case_expr() {
3440        let node = proto_case_node(
3441            Some(Box::new(column_node("case"))),
3442            vec![proto_when_then(
3443                Some(column_node("when")),
3444                Some(column_node("then")),
3445            )],
3446            Some(Box::new(column_node("else"))),
3447        );
3448        let schema = Schema::empty();
3449        let decoder = StubDecoder::ok();
3450        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
3451
3452        let decoded = CaseExpr::try_from_proto(&node, &ctx).unwrap();
3453        let case = decoded
3454            .downcast_ref::<CaseExpr>()
3455            .expect("decoded expr should be a CaseExpr");
3456
3457        assert!(case.expr().is_some());
3458        assert_eq!(case.when_then_expr().len(), 1);
3459        assert!(case.else_expr().is_some());
3460    }
3461
3462    #[test]
3463    fn try_from_proto_rejects_non_case_node() {
3464        let node = column_node("a");
3465        let schema = Schema::empty();
3466        let decoder = UnreachableDecoder;
3467        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
3468
3469        let err = CaseExpr::try_from_proto(&node, &ctx).unwrap_err();
3470        assert!(
3471            matches!(err, DataFusionError::Internal(msg) if msg.contains("PhysicalExprNode is not a CaseExpr"))
3472        );
3473    }
3474
3475    #[test]
3476    fn try_from_proto_rejects_missing_when_expr() {
3477        let node = proto_case_node(
3478            None,
3479            vec![proto_when_then(None, Some(column_node("then")))],
3480            None,
3481        );
3482        let schema = Schema::empty();
3483        let decoder = UnreachableDecoder;
3484        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
3485
3486        let err = CaseExpr::try_from_proto(&node, &ctx).unwrap_err();
3487        assert!(
3488            matches!(err, DataFusionError::Internal(msg) if msg.contains("CaseExpr is missing required field 'when_expr'"))
3489        );
3490    }
3491
3492    #[test]
3493    fn try_from_proto_rejects_missing_then_expr() {
3494        let node = proto_case_node(
3495            None,
3496            vec![proto_when_then(Some(column_node("when")), None)],
3497            None,
3498        );
3499        let schema = Schema::empty();
3500        let decoder = StubDecoder::ok();
3501        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
3502
3503        let err = CaseExpr::try_from_proto(&node, &ctx).unwrap_err();
3504        assert!(
3505            matches!(err, DataFusionError::Internal(msg) if msg.contains("CaseExpr is missing required field 'then_expr'"))
3506        );
3507    }
3508
3509    #[test]
3510    fn try_from_proto_propagates_child_decode_error() {
3511        let node = proto_case_node(
3512            Some(Box::new(column_node("case"))),
3513            vec![proto_when_then(
3514                Some(column_node("when")),
3515                Some(column_node("then")),
3516            )],
3517            Some(Box::new(column_node("else"))),
3518        );
3519        let schema = Schema::empty();
3520        let decoder = StubDecoder::failing_on(2);
3521        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
3522
3523        let err = CaseExpr::try_from_proto(&node, &ctx).unwrap_err();
3524        assert!(matches!(err, DataFusionError::Internal(msg) if msg.contains("call 2")));
3525    }
3526}