use std::borrow::Cow;
use arrow::array::{Array, BooleanArray, RecordBatch, new_null_array};
use arrow::compute::kernels::zip::zip;
use arrow::compute::{and, and_not, prep_null_mask_filter};
use arrow_schema::{DataType, Schema};
use derive_visitor::{Drive, DriveMut};
use crate::expr::{ColumnarValue, Expr, try_cast};
use crate::{ILError, ILResult};
#[derive(Debug, Clone, Drive, DriveMut, PartialEq, Eq)]
pub struct Case {
pub when_then: Vec<(Box<Expr>, Box<Expr>)>,
pub else_expr: Option<Box<Expr>>,
}
impl Case {
pub fn data_type(&self, schema: &Schema) -> ILResult<DataType> {
let mut data_type = DataType::Null;
for i in 0..self.when_then.len() {
data_type = self.when_then[i].1.data_type(schema)?;
if !data_type.equals_datatype(&DataType::Null) {
break;
}
}
if data_type.equals_datatype(&DataType::Null)
&& let Some(e) = &self.else_expr
{
data_type = e.data_type(schema)?;
}
Ok(data_type)
}
pub fn eval(&self, batch: &RecordBatch) -> ILResult<ColumnarValue> {
let return_type = self.data_type(&batch.schema())?;
let mut current_value = new_null_array(&return_type, batch.num_rows());
let mut remainder = BooleanArray::from(vec![true; batch.num_rows()]);
for i in 0..self.when_then.len() {
let when_value = self.when_then[i].0.eval_selection(batch, &remainder)?;
let when_value = when_value.into_array(batch.num_rows())?;
let when_value = when_value
.as_any()
.downcast_ref::<BooleanArray>()
.ok_or_else(|| {
ILError::internal("WHEN expression did not return a BooleanArray")
})?;
let when_value = match when_value.null_count() {
0 => Cow::Borrowed(when_value),
_ => Cow::Owned(prep_null_mask_filter(when_value)),
};
let when_value = and(&when_value, &remainder)?;
if when_value.true_count() == 0 {
continue;
}
let then_value = self.when_then[i].1.eval_selection(batch, &when_value)?;
current_value = match then_value {
ColumnarValue::Scalar(then_value) => {
zip(&when_value, &then_value.to_arrow_scalar()?, ¤t_value)?
}
ColumnarValue::Array(then_value) => zip(&when_value, &then_value, ¤t_value)?,
};
remainder = and_not(&remainder, &when_value)?;
}
if let Some(e) = &self.else_expr {
let expr = try_cast(e.as_ref().clone(), &batch.schema(), return_type.clone())?;
let else_ = expr
.eval_selection(batch, &remainder)?
.into_array(batch.num_rows())?;
current_value = zip(&remainder, &else_, ¤t_value)?;
}
Ok(ColumnarValue::Array(current_value))
}
}
impl std::fmt::Display for Case {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CASE")?;
for (when, then) in &self.when_then {
write!(f, " WHEN {when} THEN {then}")?;
}
if let Some(else_expr) = &self.else_expr {
write!(f, " ELSE {else_expr}")?;
}
write!(f, " END")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use arrow::array::{Int32Array, StringArray};
use arrow_schema::Field;
use crate::expr::{col, lit};
use super::*;
#[test]
fn test_case_expr() -> Result<(), Box<dyn std::error::Error>> {
let when_then = vec![
(Box::new(col("a").eq(lit("foo"))), Box::new(lit(123i32))),
(Box::new(col("a").eq(lit("bar"))), Box::new(lit(456i32))),
];
let case_expr = Case {
when_then,
else_expr: Some(Box::new(lit(999i32))),
};
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)])?;
let result = case_expr.eval(&batch)?.into_array(batch.num_rows())?;
let result = result.as_any().downcast_ref::<Int32Array>().unwrap();
assert_eq!(result, &Int32Array::from(vec![123, 999, 999, 456]));
Ok(())
}
#[test]
fn test_case_expr_without_else() -> Result<(), Box<dyn std::error::Error>> {
let when_then = vec![
(Box::new(col("a").eq(lit("foo"))), Box::new(lit(123i32))),
(Box::new(col("a").eq(lit("bar"))), Box::new(lit(456i32))),
];
let case_expr = Case {
when_then,
else_expr: None,
};
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)])?;
let result = case_expr.eval(&batch)?.into_array(batch.num_rows())?;
let result = result.as_any().downcast_ref::<Int32Array>().unwrap();
assert_eq!(
result,
&Int32Array::from(vec![Some(123), None, None, Some(456)])
);
Ok(())
}
}