use crate::{
db::{
numeric::{
NumericArithmeticOp, apply_numeric_arithmetic_checked, coerce_numeric_decimal,
compare_numeric_eq, compare_numeric_or_strict_order,
},
query::plan::expr::{
BinaryOp, CaseWhenArm, Expr, Function, ScalarEvalFunctionShape, UnaryOp,
collapse_true_only_boolean_admission,
},
},
value::Value,
};
use std::cmp::Ordering;
enum NullableTextArg<'a> {
Null,
Text(&'a str),
}
enum NullableIntegerArg {
Null,
Integer(i64),
}
pub(in crate::db) fn eval_literal_only_expr_value(expr: &Expr) -> Option<Value> {
match expr {
Expr::Literal(value) => Some(value.clone()),
Expr::Field(_) | Expr::FieldPath(_) | Expr::Aggregate(_) => None,
Expr::FunctionCall { function, args } => eval_literal_only_function_call(*function, args),
Expr::Case {
when_then_arms,
else_expr,
} => eval_literal_only_case_expr(when_then_arms, else_expr.as_ref()),
Expr::Binary { op, left, right } => {
let left = eval_literal_only_expr_value(left.as_ref())?;
let right = eval_literal_only_expr_value(right.as_ref())?;
eval_literal_only_binary_expr(*op, &left, &right)
}
Expr::Unary { op, expr } => {
let value = eval_literal_only_expr_value(expr.as_ref())?;
eval_literal_only_unary_expr(*op, &value)
}
#[cfg(test)]
Expr::Alias { expr, .. } => eval_literal_only_expr_value(expr.as_ref()),
}
}
fn eval_literal_only_case_expr(when_then_arms: &[CaseWhenArm], else_expr: &Expr) -> Option<Value> {
for arm in when_then_arms {
let condition = eval_literal_only_expr_value(arm.condition())?;
if collapse_true_only_boolean_admission(condition, |_| ()).ok()? {
return eval_literal_only_expr_value(arm.result());
}
}
eval_literal_only_expr_value(else_expr)
}
fn eval_literal_only_function_call(function: Function, args: &[Expr]) -> Option<Value> {
let evaluated_args = args
.iter()
.map(eval_literal_only_expr_value)
.collect::<Option<Vec<_>>>()?;
match function.scalar_eval_shape() {
ScalarEvalFunctionShape::NullTest => {
eval_null_test_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::NonExecutableProjection => None,
ScalarEvalFunctionShape::UnaryText => {
eval_unary_text_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::DynamicCoalesce => {
eval_coalesce_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::DynamicNullIf => {
eval_nullif_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::UnaryNumeric => {
eval_unary_numeric_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::BinaryNumeric => {
eval_binary_numeric_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::LeftRightText => {
eval_left_right_text_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::TextPredicate => {
eval_text_predicate_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::PositionText => {
eval_position_text_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::ReplaceText => {
eval_replace_text_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::SubstringText => {
eval_substring_text_function_call(function, &evaluated_args)
}
ScalarEvalFunctionShape::NumericScale => {
eval_numeric_scale_function_call(function, &evaluated_args)
}
}
}
fn eval_literal_only_unary_expr(op: UnaryOp, value: &Value) -> Option<Value> {
if matches!(value, Value::Null) {
return Some(Value::Null);
}
match op {
UnaryOp::Not => match value {
Value::Bool(inner) => Some(Value::Bool(!inner)),
_ => None,
},
}
}
fn eval_literal_only_binary_expr(op: BinaryOp, left: &Value, right: &Value) -> Option<Value> {
match op {
BinaryOp::Or | BinaryOp::And => eval_boolean_binary_expr(op, left, right),
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Lte
| BinaryOp::Gt
| BinaryOp::Gte => eval_compare_binary_expr(op, left, right),
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div => {
if matches!(left, Value::Null) || matches!(right, Value::Null) {
return Some(Value::Null);
}
let arithmetic_op = match op {
BinaryOp::Add => NumericArithmeticOp::Add,
BinaryOp::Sub => NumericArithmeticOp::Sub,
BinaryOp::Mul => NumericArithmeticOp::Mul,
BinaryOp::Div => NumericArithmeticOp::Div,
_ => unreachable!("arithmetic dispatch drifted"),
};
apply_numeric_arithmetic_checked(arithmetic_op, left, right)
.ok()
.flatten()
.map(Value::Decimal)
}
}
}
fn eval_boolean_binary_expr(op: BinaryOp, left: &Value, right: &Value) -> Option<Value> {
match op {
BinaryOp::And => match (left, right) {
(Value::Bool(false), _) | (_, Value::Bool(false)) => Some(Value::Bool(false)),
(Value::Bool(true), Value::Bool(true)) => Some(Value::Bool(true)),
(Value::Bool(true) | Value::Null, Value::Null) | (Value::Null, Value::Bool(true)) => {
Some(Value::Null)
}
_ => None,
},
BinaryOp::Or => match (left, right) {
(Value::Bool(true), _) | (_, Value::Bool(true)) => Some(Value::Bool(true)),
(Value::Bool(false), Value::Bool(false)) => Some(Value::Bool(false)),
(Value::Bool(false) | Value::Null, Value::Null) | (Value::Null, Value::Bool(false)) => {
Some(Value::Null)
}
_ => None,
},
_ => unreachable!("boolean binary dispatch drifted"),
}
}
fn eval_compare_binary_expr(op: BinaryOp, left: &Value, right: &Value) -> Option<Value> {
if matches!(left, Value::Null) || matches!(right, Value::Null) {
return Some(Value::Null);
}
let numeric_widen_enabled =
left.supports_numeric_coercion() || right.supports_numeric_coercion();
let result = match op {
BinaryOp::Eq => {
if let Some(equal) = compare_numeric_eq(left, right) {
equal
} else if !numeric_widen_enabled {
left == right
} else {
return None;
}
}
BinaryOp::Ne => {
if let Some(equal) = compare_numeric_eq(left, right) {
!equal
} else if !numeric_widen_enabled {
left != right
} else {
return None;
}
}
BinaryOp::Lt => compare_numeric_or_strict_order(left, right).map(Ordering::is_lt)?,
BinaryOp::Lte => compare_numeric_or_strict_order(left, right).map(Ordering::is_le)?,
BinaryOp::Gt => compare_numeric_or_strict_order(left, right).map(Ordering::is_gt)?,
BinaryOp::Gte => compare_numeric_or_strict_order(left, right).map(Ordering::is_ge)?,
_ => unreachable!("compare dispatch drifted"),
};
Some(Value::Bool(result))
}
fn eval_null_test_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [value] = args else {
return None;
};
Some(
function
.boolean_null_test_kind()
.expect("null-test preview dispatch must keep one null-test kind")
.eval_value(value),
)
}
fn eval_unary_text_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [input] = args else {
return None;
};
match input {
Value::Null => Some(Value::Null),
Value::Text(text) => Some(
function
.unary_text_function_kind()
.expect("unary-text preview dispatch must keep one unary-text kind")
.eval_text(text.as_str()),
),
_ => None,
}
}
fn eval_unary_numeric_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [input] = args else {
return None;
};
match input {
Value::Null => Some(Value::Null),
value => {
let decimal = coerce_numeric_decimal(value)?;
function
.unary_numeric_function_kind()
.expect("unary-numeric preview dispatch must keep one unary-numeric kind")
.eval_decimal(decimal)
.ok()
}
}
}
fn eval_binary_numeric_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [left, right] = args else {
return None;
};
match (left, right) {
(Value::Null, _) | (_, Value::Null) => Some(Value::Null),
(left, right) => {
let left = coerce_numeric_decimal(left)?;
let right = coerce_numeric_decimal(right)?;
function
.binary_numeric_function_kind()
.expect("binary-numeric preview dispatch must keep one binary-numeric kind")
.eval_decimal(left, right)
.ok()
}
}
}
fn eval_coalesce_function_call(function: Function, args: &[Value]) -> Option<Value> {
if args.len() < 2 {
return None;
}
Some(function.eval_coalesce_values(args))
}
fn eval_nullif_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [left, right] = args else {
return None;
};
match eval_compare_binary_expr(BinaryOp::Eq, left, right)? {
Value::Bool(true) => Some(function.eval_nullif_values(left, right, true)),
Value::Bool(false) => Some(function.eval_nullif_values(left, right, false)),
_ => None,
}
}
fn eval_left_right_text_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [input, length] = args else {
return None;
};
let length = integer_value(length)?;
match (input, length) {
(Value::Null, _) | (_, NullableIntegerArg::Null) => Some(Value::Null),
(Value::Text(text), NullableIntegerArg::Integer(length)) => Some(
function
.left_right_text_function_kind()
.expect("left/right preview dispatch must keep one left/right kind")
.eval_text(text.as_str(), length),
),
_ => None,
}
}
fn eval_text_predicate_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [input, literal] = args else {
return None;
};
let literal = text_value(literal)?;
match (input, literal) {
(Value::Null, _) | (_, NullableTextArg::Null) => Some(Value::Null),
(Value::Text(text), NullableTextArg::Text(needle)) => Some(
function
.boolean_text_predicate_kind()
.expect("text-predicate preview dispatch must keep one text-predicate kind")
.eval_text(text, needle),
),
_ => None,
}
}
fn eval_position_text_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [needle, input] = args else {
return None;
};
let needle = text_value(needle)?;
match (needle, input) {
(_, Value::Null) | (NullableTextArg::Null, _) => Some(Value::Null),
(NullableTextArg::Text(needle), Value::Text(text)) => {
Some(function.eval_position_text(text, needle))
}
_ => None,
}
}
fn eval_replace_text_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [input, from, to] = args else {
return None;
};
let from = text_value(from)?;
let to = text_value(to)?;
match (input, from, to) {
(Value::Null, _, _) | (_, NullableTextArg::Null, _) | (_, _, NullableTextArg::Null) => {
Some(Value::Null)
}
(Value::Text(text), NullableTextArg::Text(from), NullableTextArg::Text(to)) => {
Some(function.eval_replace_text(text, from, to))
}
_ => None,
}
}
fn eval_substring_text_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [input, start, rest @ ..] = args else {
return None;
};
let start = integer_value(start)?;
let length = match rest {
[] => Some(None),
[length] => Some(match integer_value(length)? {
NullableIntegerArg::Null => None,
NullableIntegerArg::Integer(value) => Some(value),
}),
_ => None,
}?;
match (input, start) {
(Value::Null, _) | (_, NullableIntegerArg::Null) => Some(Value::Null),
(Value::Text(text), NullableIntegerArg::Integer(start)) => {
Some(function.eval_substring_text(text, start, length))
}
_ => None,
}
}
fn eval_numeric_scale_function_call(function: Function, args: &[Value]) -> Option<Value> {
let [input, scale] = args else {
return None;
};
let scale = integer_value(scale)?;
match (input, scale) {
(Value::Null, _) | (_, NullableIntegerArg::Null) => Some(Value::Null),
(value, NullableIntegerArg::Integer(scale)) => {
let scale = u32::try_from(scale).ok()?;
function.eval_numeric_scale(value, scale)
}
}
}
const fn text_value(value: &Value) -> Option<NullableTextArg<'_>> {
match value {
Value::Null => Some(NullableTextArg::Null),
Value::Text(text) => Some(NullableTextArg::Text(text.as_str())),
_ => None,
}
}
fn integer_value(value: &Value) -> Option<NullableIntegerArg> {
match value {
Value::Null => Some(NullableIntegerArg::Null),
Value::Int(inner) => Some(NullableIntegerArg::Integer(*inner)),
Value::Uint(inner) => Some(NullableIntegerArg::Integer(
i64::try_from(*inner).unwrap_or(i64::MAX),
)),
_ => None,
}
}