use sqlparser::ast::helpers::attached_token::AttachedToken;
use sqlparser::ast::{
BinaryOperator, CaseWhen, CastKind, DataType, ExactNumberInfo, Expr, Function, FunctionArg,
FunctionArgExpr, FunctionArgumentList, FunctionArguments, Ident, ObjectName, ObjectNamePart,
UnaryOperator, Value,
};
use crate::error::{DiffError, Result};
fn format_f64(v: f64) -> String {
debug_assert!(
v.is_finite() && v >= 0.0,
"format_f64 expects a finite, non-negative value (got {v})"
);
let s = format!("{v}");
if s.contains(['.', 'e', 'E']) {
s
} else {
format!("{s}.0")
}
}
fn raw_num(v: f64) -> Expr {
Expr::Value(Value::Number(format_f64(v), false).with_empty_span())
}
pub(crate) fn num(v: f64) -> Expr {
debug_assert!(v.is_finite(), "num expects a finite value (got {v})");
if v < 0.0 {
Expr::UnaryOp {
op: UnaryOperator::Minus,
expr: Box::new(raw_num(-v)),
}
} else {
raw_num(if v == 0.0 { 0.0 } else { v })
}
}
pub fn finite_num(v: f64) -> Result<Expr> {
if v.is_finite() {
Ok(num(v))
} else {
Err(DiffError::NotImplemented(format!(
"cannot emit a non-finite derivative constant ({v}); a non-finite \
value has no valid SQL literal"
)))
}
}
pub fn zero() -> Expr {
num(0.0)
}
pub fn one() -> Expr {
num(1.0)
}
pub fn as_const(e: &Expr) -> Option<f64> {
match e {
Expr::Value(v) => match &v.value {
Value::Number(s, _) => s.parse::<f64>().ok(),
_ => None,
},
Expr::Nested(inner) => as_const(inner),
Expr::UnaryOp {
op: UnaryOperator::Minus,
expr,
} => as_const(expr).map(|v| -v),
Expr::UnaryOp {
op: UnaryOperator::Plus,
expr,
} => as_const(expr),
Expr::Cast {
expr, data_type, ..
} if crate::engine::is_numeric_type(data_type) => as_const(expr),
_ => None,
}
}
pub fn is_zero(e: &Expr) -> bool {
matches!(as_const(e), Some(v) if v == 0.0)
}
pub fn is_one(e: &Expr) -> bool {
matches!(as_const(e), Some(v) if v == 1.0)
}
fn precedence(e: &Expr) -> u8 {
match e {
Expr::BinaryOp { op, .. } => match op {
BinaryOperator::Plus | BinaryOperator::Minus => 10,
BinaryOperator::Multiply | BinaryOperator::Divide | BinaryOperator::Modulo => 20,
_ => 20,
},
Expr::UnaryOp {
op: UnaryOperator::Minus,
..
} => 30,
_ => 100,
}
}
fn wrap(e: Expr, threshold: u8, strict: bool) -> Expr {
let needs = if strict {
precedence(&e) < threshold
} else {
precedence(&e) <= threshold
};
if needs {
Expr::Nested(Box::new(e))
} else {
e
}
}
fn binary(left: Expr, op: BinaryOperator, right: Expr) -> Expr {
let p = match op {
BinaryOperator::Plus | BinaryOperator::Minus => 10,
_ => 20,
};
Expr::BinaryOp {
left: Box::new(wrap(left, p, true)),
op,
right: Box::new(wrap(right, p, false)),
}
}
pub fn cast_double(e: Expr) -> Expr {
Expr::Cast {
kind: CastKind::Cast,
expr: Box::new(e),
data_type: DataType::Double(ExactNumberInfo::None),
array: false,
format: None,
}
}
pub fn add(a: Expr, b: Expr) -> Expr {
if is_zero(&a) {
b
} else if is_zero(&b) {
a
} else {
binary(a, BinaryOperator::Plus, b)
}
}
pub fn sub(a: Expr, b: Expr) -> Expr {
if is_zero(&b) {
a
} else if is_zero(&a) {
neg(b)
} else {
binary(a, BinaryOperator::Minus, b)
}
}
pub fn mul(a: Expr, b: Expr) -> Expr {
if is_zero(&a) || is_zero(&b) {
zero()
} else if is_one(&a) {
b
} else if is_one(&b) {
a
} else {
binary(a, BinaryOperator::Multiply, b)
}
}
pub fn div(a: Expr, b: Expr) -> Expr {
if is_zero(&a) {
zero()
} else if is_one(&b) {
a
} else {
binary(cast_double(a), BinaryOperator::Divide, b)
}
}
pub fn neg(a: Expr) -> Expr {
if is_zero(&a) {
return zero();
}
match a {
Expr::UnaryOp {
op: UnaryOperator::Minus,
expr,
} => *expr,
other => Expr::UnaryOp {
op: UnaryOperator::Minus,
expr: Box::new(wrap(other, 30, true)),
},
}
}
pub fn square(e: Expr) -> Expr {
mul(e.clone(), e)
}
pub fn sign(u: Expr) -> Expr {
let compare = |op: BinaryOperator| Expr::BinaryOp {
left: Box::new(u.clone()),
op,
right: Box::new(zero()),
};
Expr::Case {
case_token: AttachedToken::empty(),
end_token: AttachedToken::empty(),
operand: None,
conditions: vec![
CaseWhen {
condition: compare(BinaryOperator::Gt),
result: one(),
},
CaseWhen {
condition: compare(BinaryOperator::Lt),
result: num(-1.0),
},
],
else_result: Some(Box::new(zero())),
}
}
pub fn func(name: &str, args: Vec<Expr>) -> Expr {
Expr::Function(Function {
name: ObjectName(vec![ObjectNamePart::Identifier(Ident::new(name))]),
uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: args
.into_iter()
.map(|e| FunctionArg::Unnamed(FunctionArgExpr::Expr(e)))
.collect(),
clauses: vec![],
}),
filter: None,
null_treatment: None,
over: None,
within_group: vec![],
})
}
pub fn func1(name: &str, x: Expr) -> Expr {
func(name, vec![x])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn as_const_sees_through_a_numeric_cast() {
assert_eq!(as_const(&cast_double(num(3.0))), Some(3.0));
assert_eq!(as_const(&cast_double(neg(num(2.0)))), Some(-2.0));
}
#[test]
fn as_const_refuses_a_non_numeric_cast() {
let to_text = Expr::Cast {
kind: CastKind::Cast,
expr: Box::new(num(1.0)),
data_type: DataType::Varchar(None),
array: false,
format: None,
};
assert_eq!(as_const(&to_text), None);
}
#[test]
fn right_operand_of_equal_precedence_is_parenthesized() {
let a = || Expr::Identifier(Ident::new("a"));
let b = || Expr::Identifier(Ident::new("b"));
let c = || Expr::Identifier(Ident::new("c"));
assert_eq!(
mul(a(), div(b(), c())).to_string(),
"a * (CAST(b AS DOUBLE) / c)"
);
assert_eq!(mul(a(), mul(b(), c())).to_string(), "a * (b * c)");
assert_eq!(add(a(), sub(b(), c())).to_string(), "a + (b - c)");
assert_eq!(sub(a(), sub(b(), c())).to_string(), "a - (b - c)");
assert_eq!(mul(mul(a(), b()), c()).to_string(), "a * b * c");
assert_eq!(sub(sub(a(), b()), c()).to_string(), "a - b - c");
assert_eq!(mul(a(), add(b(), c())).to_string(), "a * (b + c)");
}
#[test]
fn folds_additive_zero() {
assert_eq!(add(one(), zero()).to_string(), "1.0");
assert_eq!(add(zero(), one()).to_string(), "1.0");
}
#[test]
fn folds_multiplicative_identity_and_zero() {
assert_eq!(mul(one(), num(3.0)).to_string(), "3.0");
assert_eq!(mul(num(3.0), one()).to_string(), "3.0");
assert_eq!(mul(zero(), num(3.0)).to_string(), "0.0");
}
#[test]
fn sub_zero_left_is_negation() {
assert_eq!(
sub(zero(), Expr::Identifier(Ident::new("b"))).to_string(),
"-b"
);
}
#[test]
fn precedence_wrapping_is_semantic_not_cosmetic() {
let a = Expr::Identifier(Ident::new("a"));
let b = Expr::Identifier(Ident::new("b"));
let c = Expr::Identifier(Ident::new("c"));
let e = mul(add(a, b), c);
assert_eq!(e.to_string(), "(a + b) * c");
}
#[test]
fn non_commutative_right_operand_is_parenthesized() {
let a = Expr::Identifier(Ident::new("a"));
let b = Expr::Identifier(Ident::new("b"));
let c = Expr::Identifier(Ident::new("c"));
assert_eq!(sub(a, add(b, c)).to_string(), "a - (b + c)");
}
#[test]
fn div_casts_numerator_to_double() {
let x = Expr::Identifier(Ident::new("x"));
let y = Expr::Identifier(Ident::new("y"));
assert_eq!(div(x, y).to_string(), "CAST(x AS DOUBLE) / y");
}
#[test]
fn div_by_one_folds_without_cast() {
let x = Expr::Identifier(Ident::new("x"));
assert_eq!(div(x, one()).to_string(), "x");
}
#[test]
fn num_emits_negatives_as_unary_minus() {
assert!(matches!(num(-2.0), Expr::UnaryOp { .. }));
assert_eq!(num(-2.0).to_string(), "-2.0");
assert_eq!(num(-0.5).to_string(), "-0.5");
assert_eq!(num(0.0).to_string(), "0.0"); assert_eq!(num(-0.0).to_string(), "0.0");
}
#[test]
fn finite_num_rejects_non_finite_values() {
assert_eq!(finite_num(2.0).unwrap().to_string(), "2.0");
assert!(finite_num(f64::INFINITY).is_err());
assert!(finite_num(f64::NEG_INFINITY).is_err());
assert!(finite_num(f64::NAN).is_err());
}
}