use super::{bind_constant_numeric, Expr, ExprKind, SqlColumnRef, SqlFunctionArgument, SqlValue};
use hawdb_core::numeric::{cast_numeric, NumericType};
use hawdb_core::{LogicalType, Result, Value};
pub struct NumericLiteralCoercion<'a, F> {
column_type: F,
parameters: &'a [Value],
}
impl<'a, F> NumericLiteralCoercion<'a, F>
where
F: Fn(&SqlColumnRef) -> Option<LogicalType>,
{
pub fn new(column_type: F, parameters: &'a [Value]) -> Self {
Self {
column_type,
parameters,
}
}
pub fn needed(&self, expression: &Expr) -> bool {
let mut needed = false;
expression.visit(&mut |node| needed |= self.node_needs_coercion(node));
needed
}
pub fn apply(&self, expression: &mut Expr) -> Result<()> {
expression.try_visit_mut(&mut |node| {
if !self.node_needs_coercion(node) {
return Ok(());
}
match &mut node.kind {
ExprKind::Arithmetic { left, right, .. }
| ExprKind::Compare { left, right, .. } => {
for operand in [left, right] {
coerce_literal(operand)?;
}
}
ExprKind::InList { left, values, .. } => {
coerce_literal(left)?;
for value in values {
coerce_literal(value)?;
}
}
ExprKind::Function { arguments, .. } => {
for argument in arguments {
if let SqlFunctionArgument::Expression(argument) = argument {
coerce_literal(argument)?;
}
}
}
_ => {}
}
Ok(())
})
}
fn node_needs_coercion(&self, node: &Expr) -> bool {
let mut literal = false;
if !unified_operands(node, |operand| literal |= is_decimal_literal(operand)) || !literal {
return false;
}
let mut double = false;
unified_operands(node, |operand| {
double |= self.static_type(operand) == Some(LogicalType::Float64)
});
double
}
fn static_type(&self, expression: &Expr) -> Option<LogicalType> {
match &expression.kind {
ExprKind::Column(column) => (self.column_type)(column),
ExprKind::Value(SqlValue::Literal(value)) => value.logical_type(),
ExprKind::Value(SqlValue::Parameter(position)) => position
.checked_sub(1)
.and_then(|index| self.parameters.get(index))
.and_then(Value::logical_type),
ExprKind::Value(SqlValue::Expression(expression)) | ExprKind::Negate(expression) => {
self.static_type(expression)
}
ExprKind::Arithmetic { left, right, .. } => {
let types = [self.static_type(left), self.static_type(right)];
if types.contains(&Some(LogicalType::Float64)) {
Some(LogicalType::Float64)
} else if types.contains(&Some(LogicalType::Decimal)) {
Some(LogicalType::Decimal)
} else {
types[0].or(types[1])
}
}
ExprKind::Cast { target, .. } => Some(target.logical_type()),
ExprKind::Function {
name, arguments, ..
} => {
let mut types = arguments.iter().map(|argument| match argument {
SqlFunctionArgument::Expression(argument) => self.static_type(argument),
SqlFunctionArgument::Wildcard => None,
});
match name.as_str() {
"count" => Some(LogicalType::Int64),
"sum" | "min" | "max" if arguments.len() == 1 => types.next().flatten(),
"coalesce" => {
let types = types.flatten().collect::<Vec<_>>();
if types.contains(&LogicalType::Float64) {
Some(LogicalType::Float64)
} else {
types.first().copied()
}
}
_ => None,
}
}
_ => None,
}
}
}
fn unified_operands<'e>(node: &'e Expr, mut visit: impl FnMut(&'e Expr)) -> bool {
match &node.kind {
ExprKind::Arithmetic { left, right, .. } | ExprKind::Compare { left, right, .. } => {
visit(left);
visit(right);
}
ExprKind::InList { left, values, .. } => {
visit(left);
values.iter().for_each(&mut visit);
}
ExprKind::Function {
name, arguments, ..
} if name == "coalesce" => {
for argument in arguments {
if let SqlFunctionArgument::Expression(argument) = argument {
visit(argument);
}
}
}
_ => return false,
}
true
}
fn is_decimal_literal(expression: &Expr) -> bool {
literal_type(expression) == Some(LogicalType::Decimal)
}
fn literal_type(expression: &Expr) -> Option<LogicalType> {
match &expression.kind {
ExprKind::Value(SqlValue::Literal(Value::Int(_))) => Some(LogicalType::Int64),
ExprKind::Value(SqlValue::Literal(Value::Decimal(_))) => Some(LogicalType::Decimal),
ExprKind::Value(SqlValue::Expression(expression)) | ExprKind::Negate(expression) => {
literal_type(expression)
}
ExprKind::Arithmetic { left, right, .. } => {
match (literal_type(left)?, literal_type(right)?) {
(LogicalType::Int64, LogicalType::Int64) => Some(LogicalType::Int64),
_ => Some(LogicalType::Decimal),
}
}
_ => None,
}
}
fn coerce_literal(expression: &mut Expr) -> Result<()> {
if !is_decimal_literal(expression) {
return Ok(());
}
let value = bind_constant_numeric(expression, &[])?;
let value = cast_numeric(value.as_ref(), NumericType::DoublePrecision)?;
expression.kind = ExprKind::Value(SqlValue::Literal(value));
Ok(())
}
#[cfg(test)]
#[path = "literal_coercion_tests.rs"]
mod tests;