blob: 7a33aa95c56b5ecc58d687a5aade5d0f121151a3 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use super::{Column, Literal};
use crate::expressions::case::ResultState::{Complete, Empty, Partial};
use crate::expressions::try_cast;
use crate::PhysicalExpr;
use arrow::array::*;
use arrow::compute::kernels::zip::zip;
use arrow::compute::{
is_not_null, not, nullif, prep_null_mask_filter, FilterBuilder, FilterPredicate,
SlicesIterator,
};
use arrow::datatypes::{DataType, Schema, UInt32Type, UnionMode};
use arrow::error::ArrowError;
use datafusion_common::cast::as_boolean_array;
use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRecursion};
use datafusion_common::{
exec_err, internal_datafusion_err, internal_err, DataFusionError, HashMap, HashSet,
Result, ScalarValue,
};
use datafusion_expr::ColumnarValue;
use datafusion_physical_expr_common::datum::compare_with_eq;
use itertools::Itertools;
use std::borrow::Cow;
use std::fmt::{Debug, Formatter};
use std::hash::Hash;
use std::{any::Any, sync::Arc};
type WhenThen = (Arc<dyn PhysicalExpr>, Arc<dyn PhysicalExpr>);
#[derive(Debug, Hash, PartialEq, Eq)]
enum EvalMethod {
/// CASE WHEN condition THEN result
/// [WHEN ...]
/// [ELSE result]
/// END
NoExpression(ProjectedCaseBody),
/// CASE expression
/// WHEN value THEN result
/// [WHEN ...]
/// [ELSE result]
/// END
WithExpression(ProjectedCaseBody),
/// This is a specialization for a specific use case where we can take a fast path
/// for expressions that are infallible and can be cheaply computed for the entire
/// record batch rather than just for the rows where the predicate is true.
///
/// CASE WHEN condition THEN column [ELSE NULL] END
InfallibleExprOrNull,
/// This is a specialization for a specific use case where we can take a fast path
/// if there is just one when/then pair and both the `then` and `else` expressions
/// are literal values
/// CASE WHEN condition THEN literal ELSE literal END
ScalarOrScalar,
/// This is a specialization for a specific use case where we can take a fast path
/// if there is just one when/then pair and both the `then` and `else` are expressions
///
/// CASE WHEN condition THEN expression ELSE expression END
ExpressionOrExpression(ProjectedCaseBody),
}
/// The body of a CASE expression which consists of an optional base expression, the "when/then"
/// branches and an optional "else" branch.
#[derive(Debug, Hash, PartialEq, Eq)]
struct CaseBody {
/// Optional base expression that can be compared to literal values in the "when" expressions
expr: Option<Arc<dyn PhysicalExpr>>,
/// One or more when/then expressions
when_then_expr: Vec<WhenThen>,
/// Optional "else" expression
else_expr: Option<Arc<dyn PhysicalExpr>>,
}
impl CaseBody {
/// Derives a [ProjectedCaseBody] from this [CaseBody].
fn project(&self) -> Result<ProjectedCaseBody> {
// Determine the set of columns that are used in all the expressions of the case body.
let mut used_column_indices = HashSet::<usize>::new();
let mut collect_column_indices = |expr: &Arc<dyn PhysicalExpr>| {
expr.apply(|expr| {
if let Some(column) = expr.as_any().downcast_ref::<Column>() {
used_column_indices.insert(column.index());
}
Ok(TreeNodeRecursion::Continue)
})
.expect("Closure cannot fail");
};
if let Some(e) = &self.expr {
collect_column_indices(e);
}
self.when_then_expr.iter().for_each(|(w, t)| {
collect_column_indices(w);
collect_column_indices(t);
});
if let Some(e) = &self.else_expr {
collect_column_indices(e);
}
// Construct a mapping from the original column index to the projected column index.
let column_index_map = used_column_indices
.iter()
.enumerate()
.map(|(projected, original)| (*original, projected))
.collect::<HashMap<usize, usize>>();
// Construct the projected body by rewriting each expression from the original body
// using the column index mapping.
let project = |expr: &Arc<dyn PhysicalExpr>| -> Result<Arc<dyn PhysicalExpr>> {
Arc::clone(expr)
.transform_down(|e| {
if let Some(column) = e.as_any().downcast_ref::<Column>() {
let original = column.index();
let projected = *column_index_map.get(&original).unwrap();
if projected != original {
return Ok(Transformed::yes(Arc::new(Column::new(
column.name(),
projected,
))));
}
}
Ok(Transformed::no(e))
})
.map(|t| t.data)
};
let projected_body = CaseBody {
expr: self.expr.as_ref().map(project).transpose()?,
when_then_expr: self
.when_then_expr
.iter()
.map(|(e, t)| Ok((project(e)?, project(t)?)))
.collect::<Result<Vec<_>>>()?,
else_expr: self.else_expr.as_ref().map(project).transpose()?,
};
// Construct the projection vector
let projection = column_index_map
.iter()
.sorted_by_key(|(_, v)| **v)
.map(|(k, _)| *k)
.collect::<Vec<_>>();
Ok(ProjectedCaseBody {
projection,
body: projected_body,
})
}
}
/// A derived case body that can be used to evaluate a case expression after projecting
/// record batches using a projection vector.
///
/// This is used to avoid filtering columns that are not used in the
/// input `RecordBatch` when progressively evaluating a `CASE` expression's
/// remainder batches. Filtering these columns is wasteful since for a record
/// batch of `n` rows, filtering requires at worst a copy of `n - 1` values
/// per array. If these filtered values will never be accessed, the time spent
/// producing them is better avoided.
///
/// For example, if we are evaluating the following case expression that
/// only references columns B and D:
///
/// ```sql
/// SELECT CASE WHEN B > 10 THEN D ELSE NULL END FROM (VALUES (...)) T(A, B, C, D)
/// ```
///
/// Of the 4 input columns `[A, B, C, D]`, the `CASE` expression only access `B` and `D`.
/// Filtering `A` and `C` would be unnecessary and wasteful.
///
/// If we only retain columns `B` and `D` using `RecordBatch::project` and the projection vector
/// `[1, 3]`, the indices of these two columns will change to `[0, 1]`. To evaluate the
/// case expression, it will need to be rewritten from `CASE WHEN B@1 > 10 THEN D@3 ELSE NULL END`
/// to `CASE WHEN B@0 > 10 THEN D@1 ELSE NULL END`.
///
/// The projection vector and the rewritten expression (which only differs from the original in
/// column reference indices) are held in a `ProjectedCaseBody`.
#[derive(Debug, Hash, PartialEq, Eq)]
struct ProjectedCaseBody {
projection: Vec<usize>,
body: CaseBody,
}
/// The CASE expression is similar to a series of nested if/else and there are two forms that
/// can be used. The first form consists of a series of boolean "when" expressions with
/// corresponding "then" expressions, and an optional "else" expression.
///
/// CASE WHEN condition THEN result
/// [WHEN ...]
/// [ELSE result]
/// END
///
/// The second form uses a base expression and then a series of "when" clauses that match on a
/// literal value.
///
/// CASE expression
/// WHEN value THEN result
/// [WHEN ...]
/// [ELSE result]
/// END
#[derive(Debug, Hash, PartialEq, Eq)]
pub struct CaseExpr {
/// The case expression body
body: CaseBody,
/// Evaluation method to use
eval_method: EvalMethod,
}
impl std::fmt::Display for CaseExpr {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "CASE ")?;
if let Some(e) = &self.body.expr {
write!(f, "{e} ")?;
}
for (w, t) in &self.body.when_then_expr {
write!(f, "WHEN {w} THEN {t} ")?;
}
if let Some(e) = &self.body.else_expr {
write!(f, "ELSE {e} ")?;
}
write!(f, "END")
}
}
/// This is a specialization for a specific use case where we can take a fast path
/// for expressions that are infallible and can be cheaply computed for the entire
/// record batch rather than just for the rows where the predicate is true. For now,
/// this is limited to use with Column expressions but could potentially be used for other
/// expressions in the future
fn is_cheap_and_infallible(expr: &Arc<dyn PhysicalExpr>) -> bool {
expr.as_any().is::<Column>()
}
/// Creates a [FilterPredicate] from a boolean array.
fn create_filter(predicate: &BooleanArray, optimize: bool) -> FilterPredicate {
let mut filter_builder = FilterBuilder::new(predicate);
if optimize {
// Always optimize the filter since we use them multiple times.
filter_builder = filter_builder.optimize();
}
filter_builder.build()
}
fn multiple_arrays(data_type: &DataType) -> bool {
match data_type {
DataType::Struct(fields) => {
fields.len() > 1
|| fields.len() == 1 && multiple_arrays(fields[0].data_type())
}
DataType::Union(fields, UnionMode::Sparse) => !fields.is_empty(),
_ => false,
}
}
// This should be removed when https://github.com/apache/arrow-rs/pull/8693
// is merged and becomes available.
fn filter_record_batch(
record_batch: &RecordBatch,
filter: &FilterPredicate,
) -> std::result::Result<RecordBatch, ArrowError> {
let filtered_columns = record_batch
.columns()
.iter()
.map(|a| filter_array(a, filter))
.collect::<std::result::Result<Vec<_>, _>>()?;
// SAFETY: since we start from a valid RecordBatch, there's no need to revalidate the schema
// since the set of columns has not changed.
// The input column arrays all had the same length (since they're coming from a valid RecordBatch)
// and the filtering them with the same filter will produces a new set of arrays with identical
// lengths.
unsafe {
Ok(RecordBatch::new_unchecked(
record_batch.schema(),
filtered_columns,
filter.count(),
))
}
}
// This function exists purely to be able to use the same call style
// for `filter_record_batch` and `filter_array` at the point of use.
// When https://github.com/apache/arrow-rs/pull/8693 is available, replace
// both with method calls on `FilterPredicate`.
#[inline(always)]
fn filter_array(
array: &dyn Array,
filter: &FilterPredicate,
) -> std::result::Result<ArrayRef, ArrowError> {
filter.filter(array)
}
fn merge(
mask: &BooleanArray,
truthy: ColumnarValue,
falsy: ColumnarValue,
) -> std::result::Result<ArrayRef, ArrowError> {
let (truthy, truthy_is_scalar) = match truthy {
ColumnarValue::Array(a) => (a, false),
ColumnarValue::Scalar(s) => (s.to_array()?, true),
};
let (falsy, falsy_is_scalar) = match falsy {
ColumnarValue::Array(a) => (a, false),
ColumnarValue::Scalar(s) => (s.to_array()?, true),
};
if truthy_is_scalar && falsy_is_scalar {
return zip(mask, &Scalar::new(truthy), &Scalar::new(falsy));
}
let falsy = falsy.to_data();
let truthy = truthy.to_data();
let mut mutable = MutableArrayData::new(vec![&truthy, &falsy], false, truthy.len());
// the SlicesIterator slices only the true values. So the gaps left by this iterator we need to
// fill with falsy values
// keep track of how much is filled
let mut filled = 0;
let mut falsy_offset = 0;
let mut truthy_offset = 0;
SlicesIterator::new(mask).for_each(|(start, end)| {
// the gap needs to be filled with falsy values
if start > filled {
if falsy_is_scalar {
for _ in filled..start {
// Copy the first item from the 'falsy' array into the output buffer.
mutable.extend(1, 0, 1);
}
} else {
let falsy_length = start - filled;
let falsy_end = falsy_offset + falsy_length;
mutable.extend(1, falsy_offset, falsy_end);
falsy_offset = falsy_end;
}
}
// fill with truthy values
if truthy_is_scalar {
for _ in start..end {
// Copy the first item from the 'truthy' array into the output buffer.
mutable.extend(0, 0, 1);
}
} else {
let truthy_length = end - start;
let truthy_end = truthy_offset + truthy_length;
mutable.extend(0, truthy_offset, truthy_end);
truthy_offset = truthy_end;
}
filled = end;
});
// the remaining part is falsy
if filled < mask.len() {
if falsy_is_scalar {
for _ in filled..mask.len() {
// Copy the first item from the 'falsy' array into the output buffer.
mutable.extend(1, 0, 1);
}
} else {
let falsy_length = mask.len() - filled;
let falsy_end = falsy_offset + falsy_length;
mutable.extend(1, falsy_offset, falsy_end);
}
}
let data = mutable.freeze();
Ok(make_array(data))
}
/// Merges elements by index from a list of [`ArrayData`], creating a new [`ColumnarValue`] from
/// those values.
///
/// Each element in `indices` is the index of an array in `values`. The `indices` array is processed
/// sequentially. The first occurrence of index value `n` will be mapped to the first
/// value of the array at index `n`. The second occurrence to the second value, and so on.
/// An index value where `PartialResultIndex::is_none` is `true` is used to indicate null values.
///
/// # Implementation notes
///
/// This algorithm is similar in nature to both `zip` and `interleave`, but there are some important
/// differences.
///
/// In contrast to `zip`, this function supports multiple input arrays. Instead of a boolean
/// selection vector, an index array is to take values from the input arrays, and a special marker
/// value is used to indicate null values.
///
/// In contrast to `interleave`, this function does not use pairs of indices. The values in
/// `indices` serve the same purpose as the first value in the pairs passed to `interleave`.
/// The index in the array is implicit and is derived from the number of times a particular array
/// index occurs.
/// The more constrained indexing mechanism used by this algorithm makes it easier to copy values
/// in contiguous slices. In the example below, the two subsequent elements from array `2` can be
/// copied in a single operation from the source array instead of copying them one by one.
/// Long spans of null values are also especially cheap because they do not need to be represented
/// in an input array.
///
/// # Safety
///
/// This function does not check that the number of occurrences of any particular array index matches
/// the length of the corresponding input array. If an array contains more values than required, the
/// spurious values will be ignored. If an array contains fewer values than necessary, this function
/// will panic.
///
/// # Example
///
/// ```text
/// ┌───────────┐ ┌─────────┐ ┌─────────┐
/// │┌─────────┐│ │ None │ │ NULL │
/// ││ A ││ ├─────────┤ ├─────────┤
/// │└─────────┘│ │ 1 │ │ B │
/// │┌─────────┐│ ├─────────┤ ├─────────┤
/// ││ B ││ │ 0 │ merge(values, indices) │ A │
/// │└─────────┘│ ├─────────┤ ─────────────────────────▶ ├─────────┤
/// │┌─────────┐│ │ None │ │ NULL │
/// ││ C ││ ├─────────┤ ├─────────┤
/// │├─────────┤│ │ 2 │ │ C │
/// ││ D ││ ├─────────┤ ├─────────┤
/// │└─────────┘│ │ 2 │ │ D │
/// └───────────┘ └─────────┘ └─────────┘
/// values indices result
/// ```
fn merge_n(values: &[ArrayData], indices: &[PartialResultIndex]) -> Result<ArrayRef> {
#[cfg(debug_assertions)]
for ix in indices {
if let Some(index) = ix.index() {
assert!(
index < values.len(),
"Index out of bounds: {} >= {}",
index,
values.len()
);
}
}
let data_refs = values.iter().collect();
let mut mutable = MutableArrayData::new(data_refs, true, indices.len());
// This loop extends the mutable array by taking slices from the partial results.
//
// take_offsets keeps track of how many values have been taken from each array.
let mut take_offsets = vec![0; values.len() + 1];
let mut start_row_ix = 0;
loop {
let array_ix = indices[start_row_ix];
// Determine the length of the slice to take.
let mut end_row_ix = start_row_ix + 1;
while end_row_ix < indices.len() && indices[end_row_ix] == array_ix {
end_row_ix += 1;
}
let slice_length = end_row_ix - start_row_ix;
// Extend mutable with either nulls or with values from the array.
match array_ix.index() {
None => mutable.extend_nulls(slice_length),
Some(index) => {
let start_offset = take_offsets[index];
let end_offset = start_offset + slice_length;
mutable.extend(index, start_offset, end_offset);
take_offsets[index] = end_offset;
}
}
if end_row_ix == indices.len() {
break;
} else {
// Set the start_row_ix for the next slice.
start_row_ix = end_row_ix;
}
}
Ok(make_array(mutable.freeze()))
}
/// An index into the partial results array that's more compact than `usize`.
///
/// `u32::MAX` is reserved as a special 'none' value. This is used instead of
/// `Option` to keep the array of indices as compact as possible.
#[derive(Copy, Clone, PartialEq, Eq)]
struct PartialResultIndex {
index: u32,
}
const NONE_VALUE: u32 = u32::MAX;
impl PartialResultIndex {
/// Returns the 'none' placeholder value.
fn none() -> Self {
Self { index: NONE_VALUE }
}
fn zero() -> Self {
Self { index: 0 }
}
/// Creates a new partial result index.
///
/// If the provided value is greater than or equal to `u32::MAX`
/// an error will be returned.
fn try_new(index: usize) -> Result<Self> {
let Ok(index) = u32::try_from(index) else {
return internal_err!("Partial result index exceeds limit");
};
if index == NONE_VALUE {
return internal_err!("Partial result index exceeds limit");
}
Ok(Self { index })
}
/// Determines if this index is the 'none' placeholder value or not.
fn is_none(&self) -> bool {
self.index == NONE_VALUE
}
/// Returns `Some(index)` if this value is not the 'none' placeholder, `None` otherwise.
fn index(&self) -> Option<usize> {
if self.is_none() {
None
} else {
Some(self.index as usize)
}
}
}
impl Debug for PartialResultIndex {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.is_none() {
write!(f, "null")
} else {
write!(f, "{}", self.index)
}
}
}
enum ResultState {
/// The final result is an array containing only null values.
Empty,
/// The final result needs to be computed by merging the data in `arrays`.
Partial {
// A `Vec` of partial results that should be merged.
// `partial_result_indices` contains indexes into this vec.
arrays: Vec<ArrayData>,
// Indicates per result row from which array in `partial_results` a value should be taken.
indices: Vec<PartialResultIndex>,
},
/// A single branch matched all input rows. When creating the final result, no further merging
/// of partial results is necessary.
Complete(ColumnarValue),
}
/// A builder for constructing result arrays for CASE expressions.
///
/// Rather than building a monolithic array containing all results, it maintains a set of
/// partial result arrays and a mapping that indicates for each row which partial array
/// contains the result value for that row.
///
/// On finish(), the builder will merge all partial results into a single array if necessary.
/// If all rows evaluated to the same array, that array can be returned directly without
/// any merging overhead.
struct ResultBuilder {
data_type: DataType,
/// The number of rows in the final result.
row_count: usize,
state: ResultState,
}
impl ResultBuilder {
/// Creates a new ResultBuilder that will produce arrays of the given data type.
///
/// The `row_count` parameter indicates the number of rows in the final result.
fn new(data_type: &DataType, row_count: usize) -> Self {
Self {
data_type: data_type.clone(),
row_count,
state: Empty,
}
}
/// Adds a result for one branch of the case expression.
///
/// `row_indices` should be a [UInt32Array] containing [RecordBatch] relative row indices
/// for which `value` contains result values.
///
/// If `value` is a scalar, the scalar value will be used as the value for each row in `row_indices`.
///
/// If `value` is an array, the values from the array and the indices from `row_indices` will be
/// processed pairwise. The lengths of `value` and `row_indices` must match.
///
/// The diagram below shows a situation where a when expression matched rows 1 and 4 of the
/// record batch. The then expression produced the value array `[A, D]`.
/// After adding this result, the result array will have been added to `partial arrays` and
/// `partial indices` will have been updated at indexes `1` and `4`.
///
/// ```text
/// ┌─────────┐ ┌─────────┐┌───────────┐ ┌─────────┐┌───────────┐
/// │ C │ │ 0: None ││┌ 0 ──────┐│ │ 0: None ││┌ 0 ──────┐│
/// ├─────────┤ ├─────────┤││ A ││ ├─────────┤││ A ││
/// │ D │ │ 1: None ││└─────────┘│ │ 1: 2 ││└─────────┘│
/// └─────────┘ ├─────────┤│┌ 1 ──────┐│ add_branch_result( ├─────────┤│┌ 1 ──────┐│
/// matching │ 2: 0 │││ B ││ row indices, │ 2: 0 │││ B ││
/// 'then' values ├─────────┤│└─────────┘│ value ├─────────┤│└─────────┘│
/// │ 3: None ││ │ ) │ 3: None ││┌ 2 ──────┐│
/// ┌─────────┐ ├─────────┤│ │ ─────────────────────────▶ ├─────────┤││ C ││
/// │ 1 │ │ 4: None ││ │ │ 4: 2 ││├─────────┤│
/// ├─────────┤ ├─────────┤│ │ ├─────────┤││ D ││
/// │ 4 │ │ 5: 1 ││ │ │ 5: 1 ││└─────────┘│
/// └─────────┘ └─────────┘└───────────┘ └─────────┘└───────────┘
/// row indices partial partial partial partial
/// indices arrays indices arrays
/// ```
fn add_branch_result(
&mut self,
row_indices: &ArrayRef,
value: ColumnarValue,
) -> Result<()> {
match value {
ColumnarValue::Array(a) => {
if a.len() != row_indices.len() {
internal_err!("Array length must match row indices length")
} else if row_indices.len() == self.row_count {
self.set_complete_result(ColumnarValue::Array(a))
} else {
self.add_partial_result(row_indices, a.to_data())
}
}
ColumnarValue::Scalar(s) => {
if row_indices.len() == self.row_count {
self.set_complete_result(ColumnarValue::Scalar(s))
} else {
self.add_partial_result(
row_indices,
s.to_array_of_size(row_indices.len())?.to_data(),
)
}
}
}
}
/// Adds a partial result array.
///
/// This method adds the given array data as a partial result and updates the index mapping
/// to indicate that the specified rows should take their values from this array.
/// The partial results will be merged into a single array when finish() is called.
fn add_partial_result(
&mut self,
row_indices: &ArrayRef,
row_values: ArrayData,
) -> Result<()> {
if row_indices.null_count() != 0 {
return internal_err!("Row indices must not contain nulls");
}
match &mut self.state {
Empty => {
let array_index = PartialResultIndex::zero();
let mut indices = vec![PartialResultIndex::none(); self.row_count];
for row_ix in row_indices.as_primitive::<UInt32Type>().values().iter() {
indices[*row_ix as usize] = array_index;
}
self.state = Partial {
arrays: vec![row_values],
indices,
};
Ok(())
}
Partial { arrays, indices } => {
let array_index = PartialResultIndex::try_new(arrays.len())?;
arrays.push(row_values);
for row_ix in row_indices.as_primitive::<UInt32Type>().values().iter() {
// This is check is only active for debug config because the callers of this method,
// `case_when_with_expr` and `case_when_no_expr`, already ensure that
// they only calculate a value for each row at most once.
#[cfg(debug_assertions)]
if !indices[*row_ix as usize].is_none() {
return internal_err!("Duplicate value for row {}", *row_ix);
}
indices[*row_ix as usize] = array_index;
}
Ok(())
}
Complete(_) => internal_err!(
"Cannot add a partial result when complete result is already set"
),
}
}
/// Sets a result that applies to all rows.
///
/// This is an optimization for cases where all rows evaluate to the same result.
/// When a complete result is set, the builder will return it directly from finish()
/// without any merging overhead.
fn set_complete_result(&mut self, value: ColumnarValue) -> Result<()> {
match &self.state {
Empty => {
self.state = Complete(value);
Ok(())
}
Partial { .. } => {
internal_err!(
"Cannot set a complete result when there are already partial results"
)
}
Complete(_) => internal_err!("Complete result already set"),
}
}
/// Finishes building the result and returns the final array.
fn finish(self) -> Result<ColumnarValue> {
match self.state {
Empty => {
// No complete result and no partial results.
// This can happen for case expressions with no else branch where no rows
// matched.
Ok(ColumnarValue::Scalar(ScalarValue::try_new_null(
&self.data_type,
)?))
}
Partial { arrays, indices } => {
// Merge partial results into a single array.
Ok(ColumnarValue::Array(merge_n(&arrays, &indices)?))
}
Complete(v) => {
// If we have a complete result, we can just return it.
Ok(v)
}
}
}
}
impl CaseExpr {
/// Create a new CASE WHEN expression
pub fn try_new(
expr: Option<Arc<dyn PhysicalExpr>>,
when_then_expr: Vec<WhenThen>,
else_expr: Option<Arc<dyn PhysicalExpr>>,
) -> Result<Self> {
// normalize null literals to None in the else_expr (this already happens
// during SQL planning, but not necessarily for other use cases)
let else_expr = match &else_expr {
Some(e) => match e.as_any().downcast_ref::<Literal>() {
Some(lit) if lit.value().is_null() => None,
_ => else_expr,
},
_ => else_expr,
};
if when_then_expr.is_empty() {
return exec_err!("There must be at least one WHEN clause");
}
let body = CaseBody {
expr,
when_then_expr,
else_expr,
};
let eval_method = if body.expr.is_some() {
EvalMethod::WithExpression(body.project()?)
} else if body.when_then_expr.len() == 1
&& is_cheap_and_infallible(&(body.when_then_expr[0].1))
&& body.else_expr.is_none()
{
EvalMethod::InfallibleExprOrNull
} else if body.when_then_expr.len() == 1
&& body.when_then_expr[0].1.as_any().is::<Literal>()
&& body.else_expr.is_some()
&& body.else_expr.as_ref().unwrap().as_any().is::<Literal>()
{
EvalMethod::ScalarOrScalar
} else if body.when_then_expr.len() == 1 && body.else_expr.is_some() {
EvalMethod::ExpressionOrExpression(body.project()?)
} else {
EvalMethod::NoExpression(body.project()?)
};
Ok(Self { body, eval_method })
}
/// Optional base expression that can be compared to literal values in the "when" expressions
pub fn expr(&self) -> Option<&Arc<dyn PhysicalExpr>> {
self.body.expr.as_ref()
}
/// One or more when/then expressions
pub fn when_then_expr(&self) -> &[WhenThen] {
&self.body.when_then_expr
}
/// Optional "else" expression
pub fn else_expr(&self) -> Option<&Arc<dyn PhysicalExpr>> {
self.body.else_expr.as_ref()
}
}
impl CaseBody {
fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
// since all then results have the same data type, we can choose any one as the
// return data type except for the null.
let mut data_type = DataType::Null;
for i in 0..self.when_then_expr.len() {
data_type = self.when_then_expr[i].1.data_type(input_schema)?;
if !data_type.equals_datatype(&DataType::Null) {
break;
}
}
// if all then results are null, we use data type of else expr instead if possible.
if data_type.equals_datatype(&DataType::Null) {
if let Some(e) = &self.else_expr {
data_type = e.data_type(input_schema)?;
}
}
Ok(data_type)
}
/// See [CaseExpr::case_when_with_expr].
fn case_when_with_expr(
&self,
batch: &RecordBatch,
return_type: &DataType,
) -> Result<ColumnarValue> {
let mut result_builder = ResultBuilder::new(return_type, batch.num_rows());
// `remainder_rows` contains the indices of the rows that need to be evaluated
let mut remainder_rows: ArrayRef =
Arc::new(UInt32Array::from_iter_values(0..batch.num_rows() as u32));
// `remainder_batch` contains the rows themselves that need to be evaluated
let mut remainder_batch = Cow::Borrowed(batch);
// evaluate the base expression
let mut base_values = self
.expr
.as_ref()
.unwrap()
.evaluate(batch)?
.into_array(batch.num_rows())?;
// Fill in a result value already for rows where the base expression value is null
// Since each when expression is tested against the base expression using the equality
// operator, null base values can never match any when expression. `x = NULL` is falsy,
// for all possible values of `x`.
if base_values.null_count() > 0 {
// Use `is_not_null` since this is a cheap clone of the null buffer from 'base_value'.
// We already checked there are nulls, so we can be sure a new buffer will not be
// created.
let base_not_nulls = is_not_null(base_values.as_ref())?;
let base_all_null = base_values.null_count() == remainder_batch.num_rows();
// If there is an else expression, use that as the default value for the null rows
// Otherwise the default `null` value from the result builder will be used.
if let Some(e) = &self.else_expr {
let expr = try_cast(Arc::clone(e), &batch.schema(), return_type.clone())?;
if base_all_null {
// All base values were null, so no need to filter
let nulls_value = expr.evaluate(&remainder_batch)?;
result_builder.add_branch_result(&remainder_rows, nulls_value)?;
} else {
// Filter out the null rows and evaluate the else expression for those
let nulls_filter = create_filter(&not(&base_not_nulls)?, true);
let nulls_batch =
filter_record_batch(&remainder_batch, &nulls_filter)?;
let nulls_rows = filter_array(&remainder_rows, &nulls_filter)?;
let nulls_value = expr.evaluate(&nulls_batch)?;
result_builder.add_branch_result(&nulls_rows, nulls_value)?;
}
}
// All base values are null, so we can return early
if base_all_null {
return result_builder.finish();
}
// Remove the null rows from the remainder batch
let not_null_filter = create_filter(&base_not_nulls, true);
remainder_batch =
Cow::Owned(filter_record_batch(&remainder_batch, &not_null_filter)?);
remainder_rows = filter_array(&remainder_rows, &not_null_filter)?;
base_values = filter_array(&base_values, &not_null_filter)?;
}
// The types of case and when expressions will be coerced to match.
// We only need to check if the base_value is nested.
let base_value_is_nested = base_values.data_type().is_nested();
for i in 0..self.when_then_expr.len() {
// Evaluate the 'when' predicate for the remainder batch
// This results in a boolean array with the same length as the remaining number of rows
let when_expr = &self.when_then_expr[i].0;
let when_value = match when_expr.evaluate(&remainder_batch)? {
ColumnarValue::Array(a) => {
compare_with_eq(&a, &base_values, base_value_is_nested)
}
ColumnarValue::Scalar(s) => {
compare_with_eq(&s.to_scalar()?, &base_values, base_value_is_nested)
}
}?;
// `true_count` ignores `true` values where the validity bit is not set, so there's
// no need to call `prep_null_mask_filter`.
let when_true_count = when_value.true_count();
// If the 'when' predicate did not match any rows, continue to the next branch immediately
if when_true_count == 0 {
continue;
}
// If the 'when' predicate matched all remaining rows, there is no need to filter
if when_true_count == remainder_batch.num_rows() {
let then_expression = &self.when_then_expr[i].1;
let then_value = then_expression.evaluate(&remainder_batch)?;
result_builder.add_branch_result(&remainder_rows, then_value)?;
return result_builder.finish();
}
// Filter the remainder batch based on the 'when' value
// This results in a batch containing only the rows that need to be evaluated
// for the current branch
// Still no need to call `prep_null_mask_filter` since `create_filter` will already do
// this unconditionally.
let then_filter = create_filter(&when_value, true);
let then_batch = filter_record_batch(&remainder_batch, &then_filter)?;
let then_rows = filter_array(&remainder_rows, &then_filter)?;
let then_expression = &self.when_then_expr[i].1;
let then_value = then_expression.evaluate(&then_batch)?;
result_builder.add_branch_result(&then_rows, then_value)?;
// If this is the last 'when' branch and there is no 'else' expression, there's no
// point in calculating the remaining rows.
if self.else_expr.is_none() && i == self.when_then_expr.len() - 1 {
return result_builder.finish();
}
// Prepare the next when branch (or the else branch)
let next_selection = match when_value.null_count() {
0 => not(&when_value),
_ => {
// `prep_null_mask_filter` is required to ensure the not operation treats nulls
// as false
not(&prep_null_mask_filter(&when_value))
}
}?;
let next_filter = create_filter(&next_selection, true);
remainder_batch =
Cow::Owned(filter_record_batch(&remainder_batch, &next_filter)?);
remainder_rows = filter_array(&remainder_rows, &next_filter)?;
base_values = filter_array(&base_values, &next_filter)?;
}
// If we reached this point, some rows were left unmatched.
// Check if those need to be evaluated using the 'else' expression.
if let Some(e) = &self.else_expr {
// keep `else_expr`'s data type and return type consistent
let expr = try_cast(Arc::clone(e), &batch.schema(), return_type.clone())?;
let else_value = expr.evaluate(&remainder_batch)?;
result_builder.add_branch_result(&remainder_rows, else_value)?;
}
result_builder.finish()
}
/// See [CaseExpr::case_when_no_expr].
fn case_when_no_expr(
&self,
batch: &RecordBatch,
return_type: &DataType,
) -> Result<ColumnarValue> {
let mut result_builder = ResultBuilder::new(return_type, batch.num_rows());
// `remainder_rows` contains the indices of the rows that need to be evaluated
let mut remainder_rows: ArrayRef =
Arc::new(UInt32Array::from_iter(0..batch.num_rows() as u32));
// `remainder_batch` contains the rows themselves that need to be evaluated
let mut remainder_batch = Cow::Borrowed(batch);
for i in 0..self.when_then_expr.len() {
// Evaluate the 'when' predicate for the remainder batch
// This results in a boolean array with the same length as the remaining number of rows
let when_predicate = &self.when_then_expr[i].0;
let when_value = when_predicate
.evaluate(&remainder_batch)?
.into_array(remainder_batch.num_rows())?;
let when_value = as_boolean_array(&when_value).map_err(|_| {
internal_datafusion_err!("WHEN expression did not return a BooleanArray")
})?;
// `true_count` ignores `true` values where the validity bit is not set, so there's
// no need to call `prep_null_mask_filter`.
let when_true_count = when_value.true_count();
// If the 'when' predicate did not match any rows, continue to the next branch immediately
if when_true_count == 0 {
continue;
}
// If the 'when' predicate matched all remaining rows, there is no need to filter
if when_true_count == remainder_batch.num_rows() {
let then_expression = &self.when_then_expr[i].1;
let then_value = then_expression.evaluate(&remainder_batch)?;
result_builder.add_branch_result(&remainder_rows, then_value)?;
return result_builder.finish();
}
// Filter the remainder batch based on the 'when' value
// This results in a batch containing only the rows that need to be evaluated
// for the current branch
// Still no need to call `prep_null_mask_filter` since `create_filter` will already do
// this unconditionally.
let then_filter = create_filter(when_value, true);
let then_batch = filter_record_batch(&remainder_batch, &then_filter)?;
let then_rows = filter_array(&remainder_rows, &then_filter)?;
let then_expression = &self.when_then_expr[i].1;
let then_value = then_expression.evaluate(&then_batch)?;
result_builder.add_branch_result(&then_rows, then_value)?;
// If this is the last 'when' branch and there is no 'else' expression, there's no
// point in calculating the remaining rows.
if self.else_expr.is_none() && i == self.when_then_expr.len() - 1 {
return result_builder.finish();
}
// Prepare the next when branch (or the else branch)
let next_selection = match when_value.null_count() {
0 => not(when_value),
_ => {
// `prep_null_mask_filter` is required to ensure the not operation treats nulls
// as false
not(&prep_null_mask_filter(when_value))
}
}?;
let next_filter = create_filter(&next_selection, true);
remainder_batch =
Cow::Owned(filter_record_batch(&remainder_batch, &next_filter)?);
remainder_rows = filter_array(&remainder_rows, &next_filter)?;
}
// If we reached this point, some rows were left unmatched.
// Check if those need to be evaluated using the 'else' expression.
if let Some(e) = &self.else_expr {
// keep `else_expr`'s data type and return type consistent
let expr = try_cast(Arc::clone(e), &batch.schema(), return_type.clone())?;
let else_value = expr.evaluate(&remainder_batch)?;
result_builder.add_branch_result(&remainder_rows, else_value)?;
}
result_builder.finish()
}
/// See [CaseExpr::expr_or_expr].
fn expr_or_expr(
&self,
batch: &RecordBatch,
when_value: &BooleanArray,
) -> Result<ColumnarValue> {
let when_value = match when_value.null_count() {
0 => Cow::Borrowed(when_value),
_ => {
// `prep_null_mask_filter` is required to ensure null is treated as false
Cow::Owned(prep_null_mask_filter(when_value))
}
};
let optimize_filter = batch.num_columns() > 1
|| (batch.num_columns() == 1 && multiple_arrays(batch.column(0).data_type()));
let when_filter = create_filter(&when_value, optimize_filter);
let then_batch = filter_record_batch(batch, &when_filter)?;
let then_value = self.when_then_expr[0].1.evaluate(&then_batch)?;
let else_selection = not(&when_value)?;
let else_filter = create_filter(&else_selection, optimize_filter);
let else_batch = filter_record_batch(batch, &else_filter)?;
// keep `else_expr`'s data type and return type consistent
let e = self.else_expr.as_ref().unwrap();
let return_type = self.data_type(&batch.schema())?;
let else_expr = try_cast(Arc::clone(e), &batch.schema(), return_type.clone())
.unwrap_or_else(|_| Arc::clone(e));
let else_value = else_expr.evaluate(&else_batch)?;
Ok(ColumnarValue::Array(merge(
&when_value,
then_value,
else_value,
)?))
}
}
impl CaseExpr {
/// This function evaluates the form of CASE that matches an expression to fixed values.
///
/// CASE expression
/// WHEN value THEN result
/// [WHEN ...]
/// [ELSE result]
/// END
fn case_when_with_expr(
&self,
batch: &RecordBatch,
projected: &ProjectedCaseBody,
) -> Result<ColumnarValue> {
let return_type = self.data_type(&batch.schema())?;
if projected.projection.len() < batch.num_columns() {
let projected_batch = batch.project(&projected.projection)?;
projected
.body
.case_when_with_expr(&projected_batch, &return_type)
} else {
self.body.case_when_with_expr(batch, &return_type)
}
}
/// This function evaluates the form of CASE where each WHEN expression is a boolean
/// expression.
///
/// CASE WHEN condition THEN result
/// [WHEN ...]
/// [ELSE result]
/// END
fn case_when_no_expr(
&self,
batch: &RecordBatch,
projected: &ProjectedCaseBody,
) -> Result<ColumnarValue> {
let return_type = self.data_type(&batch.schema())?;
if projected.projection.len() < batch.num_columns() {
let projected_batch = batch.project(&projected.projection)?;
projected
.body
.case_when_no_expr(&projected_batch, &return_type)
} else {
self.body.case_when_no_expr(batch, &return_type)
}
}
/// This function evaluates the specialized case of:
///
/// CASE WHEN condition THEN column
/// [ELSE NULL]
/// END
///
/// Note that this function is only safe to use for "then" expressions
/// that are infallible because the expression will be evaluated for all
/// rows in the input batch.
fn case_column_or_null(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
let when_expr = &self.body.when_then_expr[0].0;
let then_expr = &self.body.when_then_expr[0].1;
match when_expr.evaluate(batch)? {
// WHEN true --> column
ColumnarValue::Scalar(ScalarValue::Boolean(Some(true))) => {
then_expr.evaluate(batch)
}
// WHEN [false | null] --> NULL
ColumnarValue::Scalar(_) => {
// return scalar NULL value
ScalarValue::try_from(self.data_type(&batch.schema())?)
.map(ColumnarValue::Scalar)
}
// WHEN column --> column
ColumnarValue::Array(bit_mask) => {
let bit_mask = bit_mask
.as_any()
.downcast_ref::<BooleanArray>()
.expect("predicate should evaluate to a boolean array");
// invert the bitmask
let bit_mask = match bit_mask.null_count() {
0 => not(bit_mask)?,
_ => not(&prep_null_mask_filter(bit_mask))?,
};
match then_expr.evaluate(batch)? {
ColumnarValue::Array(array) => {
Ok(ColumnarValue::Array(nullif(&array, &bit_mask)?))
}
ColumnarValue::Scalar(_) => {
internal_err!("expression did not evaluate to an array")
}
}
}
}
}
fn scalar_or_scalar(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
let return_type = self.data_type(&batch.schema())?;
// evaluate when expression
let when_value = self.body.when_then_expr[0].0.evaluate(batch)?;
let when_value = when_value.into_array(batch.num_rows())?;
let when_value = as_boolean_array(&when_value).map_err(|_| {
internal_datafusion_err!("WHEN expression did not return a BooleanArray")
})?;
// Treat 'NULL' as false value
let when_value = match when_value.null_count() {
0 => Cow::Borrowed(when_value),
_ => Cow::Owned(prep_null_mask_filter(when_value)),
};
// evaluate then_value
let then_value = self.body.when_then_expr[0].1.evaluate(batch)?;
let then_value = Scalar::new(then_value.into_array(1)?);
let Some(e) = &self.body.else_expr else {
return internal_err!("expression did not evaluate to an array");
};
// keep `else_expr`'s data type and return type consistent
let expr = try_cast(Arc::clone(e), &batch.schema(), return_type)?;
let else_ = Scalar::new(expr.evaluate(batch)?.into_array(1)?);
Ok(ColumnarValue::Array(zip(&when_value, &then_value, &else_)?))
}
fn expr_or_expr(
&self,
batch: &RecordBatch,
projected: &ProjectedCaseBody,
) -> Result<ColumnarValue> {
// evaluate when condition on batch
let when_value = self.body.when_then_expr[0].0.evaluate(batch)?;
// `num_rows == 1` is intentional to avoid expanding scalars.
// If the `when_value` is effectively a scalar, the 'all true' and 'all false' checks
// below will avoid incorrectly using the scalar as a merge/zip mask.
let when_value = when_value.into_array(1)?;
let when_value = as_boolean_array(&when_value).map_err(|e| {
DataFusionError::Context(
"WHEN expression did not return a BooleanArray".to_string(),
Box::new(e),
)
})?;
let true_count = when_value.true_count();
if true_count == when_value.len() {
// All input rows are true, just call the 'then' expression
self.body.when_then_expr[0].1.evaluate(batch)
} else if true_count == 0 {
// All input rows are false/null, just call the 'else' expression
self.body.else_expr.as_ref().unwrap().evaluate(batch)
} else if projected.projection.len() < batch.num_columns() {
// The case expressions do not use all the columns of the input batch.
// Project first to reduce time spent filtering.
let projected_batch = batch.project(&projected.projection)?;
projected.body.expr_or_expr(&projected_batch, when_value)
} else {
// All columns are used in the case expressions, so there is no need to project.
self.body.expr_or_expr(batch, when_value)
}
}
}
impl PhysicalExpr for CaseExpr {
/// Return a reference to Any that can be used for down-casting
fn as_any(&self) -> &dyn Any {
self
}
fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
self.body.data_type(input_schema)
}
fn nullable(&self, input_schema: &Schema) -> Result<bool> {
// this expression is nullable if any of the input expressions are nullable
let then_nullable = self
.body
.when_then_expr
.iter()
.map(|(_, t)| t.nullable(input_schema))
.collect::<Result<Vec<_>>>()?;
if then_nullable.contains(&true) {
Ok(true)
} else if let Some(e) = &self.body.else_expr {
e.nullable(input_schema)
} else {
// CASE produces NULL if there is no `else` expr
// (aka when none of the `when_then_exprs` match)
Ok(true)
}
}
fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
match &self.eval_method {
EvalMethod::WithExpression(p) => {
// this use case evaluates "expr" and then compares the values with the "when"
// values
self.case_when_with_expr(batch, p)
}
EvalMethod::NoExpression(p) => {
// The "when" conditions all evaluate to boolean in this use case and can be
// arbitrary expressions
self.case_when_no_expr(batch, p)
}
EvalMethod::InfallibleExprOrNull => {
// Specialization for CASE WHEN expr THEN column [ELSE NULL] END
self.case_column_or_null(batch)
}
EvalMethod::ScalarOrScalar => self.scalar_or_scalar(batch),
EvalMethod::ExpressionOrExpression(p) => self.expr_or_expr(batch, p),
}
}
fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
let mut children = vec![];
if let Some(expr) = &self.body.expr {
children.push(expr)
}
self.body.when_then_expr.iter().for_each(|(cond, value)| {
children.push(cond);
children.push(value);
});
if let Some(else_expr) = &self.body.else_expr {
children.push(else_expr)
}
children
}
// For physical CaseExpr, we do not allow modifying children size
fn with_new_children(
self: Arc<Self>,
children: Vec<Arc<dyn PhysicalExpr>>,
) -> Result<Arc<dyn PhysicalExpr>> {
if children.len() != self.children().len() {
internal_err!("CaseExpr: Wrong number of children")
} else {
let (expr, when_then_expr, else_expr) =
match (self.expr().is_some(), self.body.else_expr.is_some()) {
(true, true) => (
Some(&children[0]),
&children[1..children.len() - 1],
Some(&children[children.len() - 1]),
),
(true, false) => {
(Some(&children[0]), &children[1..children.len()], None)
}
(false, true) => (
None,
&children[0..children.len() - 1],
Some(&children[children.len() - 1]),
),
(false, false) => (None, &children[0..children.len()], None),
};
Ok(Arc::new(CaseExpr::try_new(
expr.cloned(),
when_then_expr.iter().cloned().tuples().collect(),
else_expr.cloned(),
)?))
}
}
fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "CASE ")?;
if let Some(e) = &self.body.expr {
e.fmt_sql(f)?;
write!(f, " ")?;
}
for (w, t) in &self.body.when_then_expr {
write!(f, "WHEN ")?;
w.fmt_sql(f)?;
write!(f, " THEN ")?;
t.fmt_sql(f)?;
write!(f, " ")?;
}
if let Some(e) = &self.body.else_expr {
write!(f, "ELSE ")?;
e.fmt_sql(f)?;
write!(f, " ")?;
}
write!(f, "END")
}
}
/// Create a CASE expression
pub fn case(
expr: Option<Arc<dyn PhysicalExpr>>,
when_thens: Vec<WhenThen>,
else_expr: Option<Arc<dyn PhysicalExpr>>,
) -> Result<Arc<dyn PhysicalExpr>> {
Ok(Arc::new(CaseExpr::try_new(expr, when_thens, else_expr)?))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::expressions::{binary, cast, col, lit, BinaryExpr};
use arrow::buffer::Buffer;
use arrow::datatypes::DataType::Float64;
use arrow::datatypes::Field;
use datafusion_common::cast::{as_float64_array, as_int32_array};
use datafusion_common::plan_err;
use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode};
use datafusion_expr::type_coercion::binary::comparison_coercion;
use datafusion_expr::Operator;
use datafusion_physical_expr_common::physical_expr::fmt_sql;
#[test]
fn case_with_expr() -> Result<()> {
let batch = case_test_batch()?;
let schema = batch.schema();
// CASE a WHEN 'foo' THEN 123 WHEN 'bar' THEN 456 END
let when1 = lit("foo");
let then1 = lit(123i32);
let when2 = lit("bar");
let then2 = lit(456i32);
let expr = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![(when1, then1), (when2, then2)],
None,
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result = as_int32_array(&result)?;
let expected = &Int32Array::from(vec![Some(123), None, None, Some(456)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn case_with_expr_else() -> Result<()> {
let batch = case_test_batch()?;
let schema = batch.schema();
// CASE a WHEN 'foo' THEN 123 WHEN 'bar' THEN 456 ELSE 999 END
let when1 = lit("foo");
let then1 = lit(123i32);
let when2 = lit("bar");
let then2 = lit(456i32);
let else_value = lit(999i32);
let expr = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![(when1, then1), (when2, then2)],
Some(else_value),
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result = as_int32_array(&result)?;
let expected =
&Int32Array::from(vec![Some(123), Some(999), Some(999), Some(456)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn case_with_expr_divide_by_zero() -> Result<()> {
let batch = case_test_batch1()?;
let schema = batch.schema();
// CASE a when 0 THEN float64(null) ELSE 25.0 / cast(a, float64) END
let when1 = lit(0i32);
let then1 = lit(ScalarValue::Float64(None));
let else_value = binary(
lit(25.0f64),
Operator::Divide,
cast(col("a", &schema)?, &batch.schema(), Float64)?,
&batch.schema(),
)?;
let expr = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![(when1, then1)],
Some(else_value),
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result =
as_float64_array(&result).expect("failed to downcast to Float64Array");
let expected = &Float64Array::from(vec![Some(25.0), None, None, Some(5.0)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn case_without_expr() -> Result<()> {
let batch = case_test_batch()?;
let schema = batch.schema();
// CASE WHEN a = 'foo' THEN 123 WHEN a = 'bar' THEN 456 END
let when1 = binary(
col("a", &schema)?,
Operator::Eq,
lit("foo"),
&batch.schema(),
)?;
let then1 = lit(123i32);
let when2 = binary(
col("a", &schema)?,
Operator::Eq,
lit("bar"),
&batch.schema(),
)?;
let then2 = lit(456i32);
let expr = generate_case_when_with_type_coercion(
None,
vec![(when1, then1), (when2, then2)],
None,
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result = as_int32_array(&result)?;
let expected = &Int32Array::from(vec![Some(123), None, None, Some(456)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn case_with_expr_when_null() -> Result<()> {
let batch = case_test_batch()?;
let schema = batch.schema();
// CASE a WHEN NULL THEN 0 WHEN a THEN 123 ELSE 999 END
let when1 = lit(ScalarValue::Utf8(None));
let then1 = lit(0i32);
let when2 = col("a", &schema)?;
let then2 = lit(123i32);
let else_value = lit(999i32);
let expr = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![(when1, then1), (when2, then2)],
Some(else_value),
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result = as_int32_array(&result)?;
let expected =
&Int32Array::from(vec![Some(123), Some(123), Some(999), Some(123)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn case_without_expr_divide_by_zero() -> Result<()> {
let batch = case_test_batch1()?;
let schema = batch.schema();
// CASE WHEN a > 0 THEN 25.0 / cast(a, float64) ELSE float64(null) END
let when1 = binary(col("a", &schema)?, Operator::Gt, lit(0i32), &batch.schema())?;
let then1 = binary(
lit(25.0f64),
Operator::Divide,
cast(col("a", &schema)?, &batch.schema(), Float64)?,
&batch.schema(),
)?;
let x = lit(ScalarValue::Float64(None));
let expr = generate_case_when_with_type_coercion(
None,
vec![(when1, then1)],
Some(x),
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result =
as_float64_array(&result).expect("failed to downcast to Float64Array");
let expected = &Float64Array::from(vec![Some(25.0), None, None, Some(5.0)]);
assert_eq!(expected, result);
Ok(())
}
fn case_test_batch1() -> Result<RecordBatch> {
let schema = Schema::new(vec![
Field::new("a", DataType::Int32, true),
Field::new("b", DataType::Int32, true),
Field::new("c", DataType::Int32, true),
]);
let a = Int32Array::from(vec![Some(1), Some(0), None, Some(5)]);
let b = Int32Array::from(vec![Some(3), None, Some(14), Some(7)]);
let c = Int32Array::from(vec![Some(0), Some(-3), Some(777), None]);
let batch = RecordBatch::try_new(
Arc::new(schema),
vec![Arc::new(a), Arc::new(b), Arc::new(c)],
)?;
Ok(batch)
}
#[test]
fn case_without_expr_else() -> Result<()> {
let batch = case_test_batch()?;
let schema = batch.schema();
// CASE WHEN a = 'foo' THEN 123 WHEN a = 'bar' THEN 456 ELSE 999 END
let when1 = binary(
col("a", &schema)?,
Operator::Eq,
lit("foo"),
&batch.schema(),
)?;
let then1 = lit(123i32);
let when2 = binary(
col("a", &schema)?,
Operator::Eq,
lit("bar"),
&batch.schema(),
)?;
let then2 = lit(456i32);
let else_value = lit(999i32);
let expr = generate_case_when_with_type_coercion(
None,
vec![(when1, then1), (when2, then2)],
Some(else_value),
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result = as_int32_array(&result)?;
let expected =
&Int32Array::from(vec![Some(123), Some(999), Some(999), Some(456)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn case_with_type_cast() -> Result<()> {
let batch = case_test_batch()?;
let schema = batch.schema();
// CASE WHEN a = 'foo' THEN 123.3 ELSE 999 END
let when = binary(
col("a", &schema)?,
Operator::Eq,
lit("foo"),
&batch.schema(),
)?;
let then = lit(123.3f64);
let else_value = lit(999i32);
let expr = generate_case_when_with_type_coercion(
None,
vec![(when, then)],
Some(else_value),
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result =
as_float64_array(&result).expect("failed to downcast to Float64Array");
let expected =
&Float64Array::from(vec![Some(123.3), Some(999.0), Some(999.0), Some(999.0)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn case_with_matches_and_nulls() -> Result<()> {
let batch = case_test_batch_nulls()?;
let schema = batch.schema();
// SELECT CASE WHEN load4 = 1.77 THEN load4 END
let when = binary(
col("load4", &schema)?,
Operator::Eq,
lit(1.77f64),
&batch.schema(),
)?;
let then = col("load4", &schema)?;
let expr = generate_case_when_with_type_coercion(
None,
vec![(when, then)],
None,
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result =
as_float64_array(&result).expect("failed to downcast to Float64Array");
let expected =
&Float64Array::from(vec![Some(1.77), None, None, None, None, Some(1.77)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn case_with_scalar_predicate() -> Result<()> {
let batch = case_test_batch_nulls()?;
let schema = batch.schema();
// SELECT CASE WHEN TRUE THEN load4 END
let when = lit(true);
let then = col("load4", &schema)?;
let expr = generate_case_when_with_type_coercion(
None,
vec![(when, then)],
None,
schema.as_ref(),
)?;
// many rows
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result =
as_float64_array(&result).expect("failed to downcast to Float64Array");
let expected = &Float64Array::from(vec![
Some(1.77),
None,
None,
Some(1.78),
None,
Some(1.77),
]);
assert_eq!(expected, result);
// one row
let expected = Float64Array::from(vec![Some(1.1)]);
let batch =
RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(expected.clone())])?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result =
as_float64_array(&result).expect("failed to downcast to Float64Array");
assert_eq!(&expected, result);
Ok(())
}
#[test]
fn case_expr_matches_and_nulls() -> Result<()> {
let batch = case_test_batch_nulls()?;
let schema = batch.schema();
// SELECT CASE load4 WHEN 1.77 THEN load4 END
let expr = col("load4", &schema)?;
let when = lit(1.77f64);
let then = col("load4", &schema)?;
let expr = generate_case_when_with_type_coercion(
Some(expr),
vec![(when, then)],
None,
schema.as_ref(),
)?;
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result =
as_float64_array(&result).expect("failed to downcast to Float64Array");
let expected =
&Float64Array::from(vec![Some(1.77), None, None, None, None, Some(1.77)]);
assert_eq!(expected, result);
Ok(())
}
#[test]
fn test_when_null_and_some_cond_else_null() -> Result<()> {
let batch = case_test_batch()?;
let schema = batch.schema();
let when = binary(
Arc::new(Literal::new(ScalarValue::Boolean(None))),
Operator::And,
binary(col("a", &schema)?, Operator::Eq, lit("foo"), &schema)?,
&schema,
)?;
let then = col("a", &schema)?;
// SELECT CASE WHEN (NULL AND a = 'foo') THEN a ELSE NULL END
let expr = Arc::new(CaseExpr::try_new(None, vec![(when, then)], None)?);
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result = as_string_array(&result);
// all result values should be null
assert_eq!(result.logical_null_count(), batch.num_rows());
Ok(())
}
fn case_test_batch() -> Result<RecordBatch> {
let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
let a = StringArray::from(vec![Some("foo"), Some("baz"), None, Some("bar")]);
let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)])?;
Ok(batch)
}
// Construct an array that has several NULL values whose
// underlying buffer actually matches the where expr predicate
fn case_test_batch_nulls() -> Result<RecordBatch> {
let load4: Float64Array = vec![
Some(1.77), // 1.77
Some(1.77), // null <-- same value, but will be set to null
Some(1.77), // null <-- same value, but will be set to null
Some(1.78), // 1.78
None, // null
Some(1.77), // 1.77
]
.into_iter()
.collect();
let null_buffer = Buffer::from([0b00101001u8]);
let load4 = load4
.into_data()
.into_builder()
.null_bit_buffer(Some(null_buffer))
.build()
.unwrap();
let load4: Float64Array = load4.into();
let batch =
RecordBatch::try_from_iter(vec![("load4", Arc::new(load4) as ArrayRef)])?;
Ok(batch)
}
#[test]
fn case_test_incompatible() -> Result<()> {
// 1 then is int64
// 2 then is boolean
let batch = case_test_batch()?;
let schema = batch.schema();
// CASE WHEN a = 'foo' THEN 123 WHEN a = 'bar' THEN true END
let when1 = binary(
col("a", &schema)?,
Operator::Eq,
lit("foo"),
&batch.schema(),
)?;
let then1 = lit(123i32);
let when2 = binary(
col("a", &schema)?,
Operator::Eq,
lit("bar"),
&batch.schema(),
)?;
let then2 = lit(true);
let expr = generate_case_when_with_type_coercion(
None,
vec![(when1, then1), (when2, then2)],
None,
schema.as_ref(),
);
assert!(expr.is_err());
// then 1 is int32
// then 2 is int64
// else is float
// CASE WHEN a = 'foo' THEN 123 WHEN a = 'bar' THEN 456 ELSE 1.23 END
let when1 = binary(
col("a", &schema)?,
Operator::Eq,
lit("foo"),
&batch.schema(),
)?;
let then1 = lit(123i32);
let when2 = binary(
col("a", &schema)?,
Operator::Eq,
lit("bar"),
&batch.schema(),
)?;
let then2 = lit(456i64);
let else_expr = lit(1.23f64);
let expr = generate_case_when_with_type_coercion(
None,
vec![(when1, then1), (when2, then2)],
Some(else_expr),
schema.as_ref(),
);
assert!(expr.is_ok());
let result_type = expr.unwrap().data_type(schema.as_ref())?;
assert_eq!(Float64, result_type);
Ok(())
}
#[test]
fn case_eq() -> Result<()> {
let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
let when1 = lit("foo");
let then1 = lit(123i32);
let when2 = lit("bar");
let then2 = lit(456i32);
let else_value = lit(999i32);
let expr1 = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![
(Arc::clone(&when1), Arc::clone(&then1)),
(Arc::clone(&when2), Arc::clone(&then2)),
],
Some(Arc::clone(&else_value)),
&schema,
)?;
let expr2 = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![
(Arc::clone(&when1), Arc::clone(&then1)),
(Arc::clone(&when2), Arc::clone(&then2)),
],
Some(Arc::clone(&else_value)),
&schema,
)?;
let expr3 = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![(Arc::clone(&when1), Arc::clone(&then1)), (when2, then2)],
None,
&schema,
)?;
let expr4 = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![(when1, then1)],
Some(else_value),
&schema,
)?;
assert!(expr1.eq(&expr2));
assert!(expr2.eq(&expr1));
assert!(expr2.ne(&expr3));
assert!(expr3.ne(&expr2));
assert!(expr1.ne(&expr4));
assert!(expr4.ne(&expr1));
Ok(())
}
#[test]
fn case_transform() -> Result<()> {
let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
let when1 = lit("foo");
let then1 = lit(123i32);
let when2 = lit("bar");
let then2 = lit(456i32);
let else_value = lit(999i32);
let expr = generate_case_when_with_type_coercion(
Some(col("a", &schema)?),
vec![
(Arc::clone(&when1), Arc::clone(&then1)),
(Arc::clone(&when2), Arc::clone(&then2)),
],
Some(Arc::clone(&else_value)),
&schema,
)?;
let expr2 = Arc::clone(&expr)
.transform(|e| {
let transformed = match e.as_any().downcast_ref::<Literal>() {
Some(lit_value) => match lit_value.value() {
ScalarValue::Utf8(Some(str_value)) => {
Some(lit(str_value.to_uppercase()))
}
_ => None,
},
_ => None,
};
Ok(if let Some(transformed) = transformed {
Transformed::yes(transformed)
} else {
Transformed::no(e)
})
})
.data()
.unwrap();
let expr3 = Arc::clone(&expr)
.transform_down(|e| {
let transformed = match e.as_any().downcast_ref::<Literal>() {
Some(lit_value) => match lit_value.value() {
ScalarValue::Utf8(Some(str_value)) => {
Some(lit(str_value.to_uppercase()))
}
_ => None,
},
_ => None,
};
Ok(if let Some(transformed) = transformed {
Transformed::yes(transformed)
} else {
Transformed::no(e)
})
})
.data()
.unwrap();
assert!(expr.ne(&expr2));
assert!(expr2.eq(&expr3));
Ok(())
}
#[test]
fn test_column_or_null_specialization() -> Result<()> {
// create input data
let mut c1 = Int32Builder::new();
let mut c2 = StringBuilder::new();
for i in 0..1000 {
c1.append_value(i);
if i % 7 == 0 {
c2.append_null();
} else {
c2.append_value(format!("string {i}"));
}
}
let c1 = Arc::new(c1.finish());
let c2 = Arc::new(c2.finish());
let schema = Schema::new(vec![
Field::new("c1", DataType::Int32, true),
Field::new("c2", DataType::Utf8, true),
]);
let batch = RecordBatch::try_new(Arc::new(schema), vec![c1, c2]).unwrap();
// CaseWhenExprOrNull should produce same results as CaseExpr
let predicate = Arc::new(BinaryExpr::new(
make_col("c1", 0),
Operator::LtEq,
make_lit_i32(250),
));
let expr = CaseExpr::try_new(None, vec![(predicate, make_col("c2", 1))], None)?;
assert!(matches!(expr.eval_method, EvalMethod::InfallibleExprOrNull));
match expr.evaluate(&batch)? {
ColumnarValue::Array(array) => {
assert_eq!(1000, array.len());
assert_eq!(785, array.null_count());
}
_ => unreachable!(),
}
Ok(())
}
#[test]
fn test_expr_or_expr_specialization() -> Result<()> {
let batch = case_test_batch1()?;
let schema = batch.schema();
let when = binary(
col("a", &schema)?,
Operator::LtEq,
lit(2i32),
&batch.schema(),
)?;
let then = col("b", &schema)?;
let else_expr = col("c", &schema)?;
let expr = CaseExpr::try_new(None, vec![(when, then)], Some(else_expr))?;
assert!(matches!(
expr.eval_method,
EvalMethod::ExpressionOrExpression(_)
));
let result = expr
.evaluate(&batch)?
.into_array(batch.num_rows())
.expect("Failed to convert to array");
let result = as_int32_array(&result).expect("failed to downcast to Int32Array");
let expected = &Int32Array::from(vec![Some(3), None, Some(777), None]);
assert_eq!(expected, result);
Ok(())
}
fn make_col(name: &str, index: usize) -> Arc<dyn PhysicalExpr> {
Arc::new(Column::new(name, index))
}
fn make_lit_i32(n: i32) -> Arc<dyn PhysicalExpr> {
Arc::new(Literal::new(ScalarValue::Int32(Some(n))))
}
fn generate_case_when_with_type_coercion(
expr: Option<Arc<dyn PhysicalExpr>>,
when_thens: Vec<WhenThen>,
else_expr: Option<Arc<dyn PhysicalExpr>>,
input_schema: &Schema,
) -> Result<Arc<dyn PhysicalExpr>> {
let coerce_type =
get_case_common_type(&when_thens, else_expr.clone(), input_schema);
let (when_thens, else_expr) = match coerce_type {
None => plan_err!(
"Can't get a common type for then {when_thens:?} and else {else_expr:?} expression"
),
Some(data_type) => {
// cast then expr
let left = when_thens
.into_iter()
.map(|(when, then)| {
let then = try_cast(then, input_schema, data_type.clone())?;
Ok((when, then))
})
.collect::<Result<Vec<_>>>()?;
let right = match else_expr {
None => None,
Some(expr) => Some(try_cast(expr, input_schema, data_type.clone())?),
};
Ok((left, right))
}
}?;
case(expr, when_thens, else_expr)
}
fn get_case_common_type(
when_thens: &[WhenThen],
else_expr: Option<Arc<dyn PhysicalExpr>>,
input_schema: &Schema,
) -> Option<DataType> {
let thens_type = when_thens
.iter()
.map(|when_then| {
let data_type = &when_then.1.data_type(input_schema).unwrap();
data_type.clone()
})
.collect::<Vec<_>>();
let else_type = match else_expr {
None => {
// case when then exprs must have one then value
thens_type[0].clone()
}
Some(else_phy_expr) => else_phy_expr.data_type(input_schema).unwrap(),
};
thens_type
.iter()
.try_fold(else_type, |left_type, right_type| {
// TODO: now just use the `equal` coercion rule for case when. If find the issue, and
// refactor again.
comparison_coercion(&left_type, right_type)
})
}
#[test]
fn test_fmt_sql() -> Result<()> {
let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
// CASE WHEN a = 'foo' THEN 123.3 ELSE 999 END
let when = binary(col("a", &schema)?, Operator::Eq, lit("foo"), &schema)?;
let then = lit(123.3f64);
let else_value = lit(999i32);
let expr = generate_case_when_with_type_coercion(
None,
vec![(when, then)],
Some(else_value),
&schema,
)?;
let display_string = expr.to_string();
assert_eq!(
display_string,
"CASE WHEN a@0 = foo THEN 123.3 ELSE TRY_CAST(999 AS Float64) END"
);
let sql_string = fmt_sql(expr.as_ref()).to_string();
assert_eq!(
sql_string,
"CASE WHEN a = foo THEN 123.3 ELSE TRY_CAST(999 AS Float64) END"
);
Ok(())
}
#[test]
fn test_merge_n() {
let a1 = StringArray::from(vec![Some("A")]).to_data();
let a2 = StringArray::from(vec![Some("B")]).to_data();
let a3 = StringArray::from(vec![Some("C"), Some("D")]).to_data();
let indices = vec![
PartialResultIndex::none(),
PartialResultIndex::try_new(1).unwrap(),
PartialResultIndex::try_new(0).unwrap(),
PartialResultIndex::none(),
PartialResultIndex::try_new(2).unwrap(),
PartialResultIndex::try_new(2).unwrap(),
];
let merged = merge_n(&[a1, a2, a3], &indices).unwrap();
let merged = merged.as_string::<i32>();
assert_eq!(merged.len(), indices.len());
assert!(!merged.is_valid(0));
assert!(merged.is_valid(1));
assert_eq!(merged.value(1), "B");
assert!(merged.is_valid(2));
assert_eq!(merged.value(2), "A");
assert!(!merged.is_valid(3));
assert!(merged.is_valid(4));
assert_eq!(merged.value(4), "C");
assert!(merged.is_valid(5));
assert_eq!(merged.value(5), "D");
}
#[test]
fn test_merge() {
let a1 = Arc::new(StringArray::from(vec![Some("A"), Some("C")]));
let a2 = Arc::new(StringArray::from(vec![Some("B")]));
let mask = BooleanArray::from(vec![true, false, true]);
let merged =
merge(&mask, ColumnarValue::Array(a1), ColumnarValue::Array(a2)).unwrap();
let merged = merged.as_string::<i32>();
assert_eq!(merged.len(), mask.len());
assert!(merged.is_valid(0));
assert_eq!(merged.value(0), "A");
assert!(merged.is_valid(1));
assert_eq!(merged.value(1), "B");
assert!(merged.is_valid(2));
assert_eq!(merged.value(2), "C");
}
}