use super::scalar::{expression_numeric_kind, NumericKind};
use super::*;
use crate::expressions::RoundFunc;
fn combine(left: NumericKind, right: NumericKind) -> NumericKind {
use NumericKind::*;
match (left, right) {
(Unknown, _) | (_, Unknown) => Unknown,
(Float, _) | (_, Float) => Float,
(Decimal, _) | (_, Decimal) => Decimal,
(Integer, Integer) => Integer,
}
}
fn numeric_kind(expr: &Expression) -> NumericKind {
match expr {
Expression::Paren(p) => numeric_kind(&p.this),
Expression::Neg(n) => numeric_kind(&n.this),
Expression::Alias(a) => numeric_kind(&a.this),
Expression::Add(b) | Expression::Sub(b) | Expression::Mul(b) | Expression::Mod(b) => {
combine(numeric_kind(&b.left), numeric_kind(&b.right))
}
Expression::IntDiv(_) => NumericKind::Integer,
Expression::Div(_) => NumericKind::Float,
Expression::Function(f) if f.name.eq_ignore_ascii_case("NULLIF") && f.args.len() == 2 => {
numeric_kind(&f.args[0])
}
Expression::NullIf(f) => numeric_kind(&f.this),
_ => expression_numeric_kind(expr),
}
}
fn datafusion_numeric_kind(expr: &Expression) -> NumericKind {
match expr {
Expression::Paren(p) => datafusion_numeric_kind(&p.this),
Expression::Annotated(a) => datafusion_numeric_kind(&a.this),
Expression::Neg(n) => datafusion_numeric_kind(&n.this),
Expression::Alias(a) => datafusion_numeric_kind(&a.this),
Expression::Abs(f) => datafusion_numeric_kind(&f.this),
Expression::Add(b) | Expression::Sub(b) | Expression::Mul(b) | Expression::Mod(b) => {
combine(
datafusion_numeric_kind(&b.left),
datafusion_numeric_kind(&b.right),
)
}
Expression::Div(b) => {
let left = datafusion_numeric_kind(&b.left);
let right = match &b.right {
Expression::Function(f)
if f.name.eq_ignore_ascii_case("NULLIF") && f.args.len() == 2 =>
{
combine(
datafusion_numeric_kind(&f.args[0]),
datafusion_numeric_kind(&f.args[1]),
)
}
other => datafusion_numeric_kind(other),
};
if left == NumericKind::Float || right == NumericKind::Float {
NumericKind::Float
} else {
combine(left, right)
}
}
Expression::Coalesce(f) => datafusion_branch_kind(f.expressions.iter()),
Expression::Case(c) => datafusion_branch_kind(
c.whens
.iter()
.map(|(_, result)| result)
.chain(c.else_.iter()),
),
Expression::IfFunc(f) => {
datafusion_branch_kind(std::iter::once(&f.true_value).chain(f.false_value.iter()))
}
Expression::Function(f) if f.name.eq_ignore_ascii_case("NULLIF") && f.args.len() == 2 => {
datafusion_nullif_kind(&f.args[0], &f.args[1])
}
Expression::NullIf(f) => datafusion_nullif_kind(&f.this, &f.expression),
_ => expression_numeric_kind(expr),
}
}
fn datafusion_branch_kind<'a>(branches: impl Iterator<Item = &'a Expression>) -> NumericKind {
branches
.filter(|expr| !matches!(expr, Expression::Null(_)))
.map(datafusion_numeric_kind)
.reduce(combine)
.unwrap_or(NumericKind::Unknown)
}
fn datafusion_nullif_kind(left: &Expression, right: &Expression) -> NumericKind {
let left_kind = datafusion_numeric_kind(left);
if left_kind == datafusion_numeric_kind(right) || matches!(right, Expression::Null(_)) {
left_kind
} else {
NumericKind::Unknown
}
}
fn as_double(expr: Expression) -> Expression {
super::operators::cast_expr(
expr,
DataType::Double {
precision: None,
scale: None,
},
)
}
fn unresolved_division(target: DialectType) -> crate::error::Error {
crate::error::Error::unsupported(
"DuckDB // with unresolved operand types; use explicit numeric casts",
target.to_string(),
)
}
fn prepare_datafusion_division(expr: Expression) -> Result<Expression> {
crate::traversal::transform_with_dispatch(
expr,
&Ok,
&|expr| !matches!(expr, Expression::IntDiv(_)),
&|expr, _| {
crate::traversal::transform_all(expr, &|mut expr| {
match &mut expr {
Expression::Div(b) => {
if datafusion_numeric_kind(&b.left) != NumericKind::Float
&& datafusion_numeric_kind(&b.right) != NumericKind::Float
{
b.left =
as_double(std::mem::replace(&mut b.left, Expression::number(0)));
}
}
Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c)
if super::scalar::data_type_numeric_kind(&c.to) == NumericKind::Integer
&& matches!(
datafusion_numeric_kind(&c.this),
NumericKind::Float | NumericKind::Decimal
) =>
{
c.this = Expression::Round(Box::new(RoundFunc {
this: std::mem::replace(&mut c.this, Expression::number(0)),
decimals: Some(Expression::number(0)),
}));
}
Expression::IntDiv(_) => {
let Expression::IntDiv(mut division) = expr else {
unreachable!()
};
let kind = combine(
datafusion_numeric_kind(&division.this),
datafusion_numeric_kind(&division.expression),
);
if kind == NumericKind::Unknown {
return Err(unresolved_division(DialectType::DataFusion));
}
if kind == NumericKind::Decimal {
division.this = as_double(division.this);
}
return Ok(Expression::Div(Box::new(BinaryOp::new(
division.this,
Expression::Function(Box::new(Function::new(
"NULLIF".to_string(),
vec![division.expression, Expression::number(0)],
))),
))));
}
_ => {}
}
Ok(expr)
})
},
)
}
pub(in crate::dialects) fn prepare_integer_division(
expr: Expression,
source: DialectType,
target: DialectType,
) -> Result<Expression> {
if source != DialectType::DuckDB || target == DialectType::DuckDB {
return Ok(expr);
}
if target == DialectType::DataFusion {
return prepare_datafusion_division(expr);
}
crate::traversal::transform_all(expr, &|expr| {
let Expression::IntDiv(mut division) = expr else {
return Ok(expr);
};
let left_kind = numeric_kind(&division.this);
let right_kind = numeric_kind(&division.expression);
let kind = combine(left_kind, right_kind);
if kind == NumericKind::Unknown {
return Err(unresolved_division(target));
}
division.expression = Expression::Function(Box::new(Function::new(
"NULLIF".to_string(),
vec![division.expression, Expression::number(0)],
)));
if kind != NumericKind::Integer {
if kind == NumericKind::Decimal {
division.this = super::operators::cast_expr(
division.this,
DataType::Double {
precision: None,
scale: None,
},
);
}
return Ok(Expression::Div(Box::new(BinaryOp::new(
division.this,
division.expression,
))));
}
if !matches!(
target,
DialectType::PostgreSQL
| DialectType::BigQuery
| DialectType::ClickHouse
| DialectType::SQLite
| DialectType::Vertica
| DialectType::Hive
| DialectType::Spark
| DialectType::Databricks
) {
return Err(crate::error::Error::unsupported(
"DuckDB integer // conversion",
target.to_string(),
));
}
Ok(Expression::IntDiv(division))
})
}