use crate::ast::Expr;
use crate::catalog::Catalog;
use crate::error::DbError;
use crate::storage::Storage;
use crate::types::Value;
use super::super::clock::Clock;
use super::super::context::ExecutionContext;
use super::super::evaluator::eval_expr;
use super::super::model::ContextTable;
use super::super::value_helpers::value_to_f64;
#[allow(clippy::too_many_arguments)]
pub(crate) fn eval_math_unary<F>(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
name: &str,
func: F,
) -> Result<Value, DbError>
where
F: Fn(f64) -> f64,
{
if args.len() != 1 {
return Err(DbError::Execution(format!("{} expects 1 argument", name)));
}
let val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
if val.is_null() {
return Ok(Value::Null);
}
match &val {
Value::Decimal(_, scale) => {
let f = value_to_f64(&val)?;
let result = func(f);
let result_raw = (result * 10f64.powi(*scale as i32)).round() as i128;
Ok(Value::Decimal(result_raw, *scale))
}
Value::Float(bits) => {
let f = f64::from_bits(*bits);
let result = func(f);
Ok(Value::Float(result.to_bits()))
}
Value::Int(v) => {
let f = *v as f64;
let result = func(f);
let raw = (result * 1000000.0).round() as i128;
Ok(Value::Decimal(raw, 6))
}
Value::BigInt(v) => {
let f = *v as f64;
let result = func(f);
let raw = (result * 1000000.0).round() as i128;
Ok(Value::Decimal(raw, 6))
}
Value::TinyInt(v) => {
let f = *v as f64;
let result = func(f);
let raw = (result * 1000000.0).round() as i128;
Ok(Value::Decimal(raw, 6))
}
Value::SmallInt(v) => {
let f = *v as f64;
let result = func(f);
let raw = (result * 1000000.0).round() as i128;
Ok(Value::Decimal(raw, 6))
}
_ => {
let f = value_to_f64(&val)?;
let result = func(f);
Ok(Value::Float(result.to_bits()))
}
}
}
#[allow(clippy::too_many_arguments)]
fn eval_math_unary_float<F>(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
name: &str,
func: F,
) -> Result<Value, DbError>
where
F: Fn(f64) -> f64,
{
if args.len() != 1 {
return Err(DbError::Execution(format!("{} expects 1 argument", name)));
}
let val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
if val.is_null() {
return Ok(Value::Null);
}
let f = value_to_f64(&val)?;
let result = func(f);
Ok(Value::Float(result.to_bits()))
}
pub(crate) fn eval_round(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
if args.is_empty() || args.len() > 2 {
return Err(DbError::Execution("ROUND expects 1 or 2 arguments".into()));
}
let val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
let precision = if args.len() == 2 {
let p = eval_expr(&args[1], row, ctx, catalog, storage, clock)?;
match p {
Value::Int(v) => v,
Value::TinyInt(v) => v as i32,
Value::SmallInt(v) => v as i32,
_ => {
return Err(DbError::Execution(
"ROUND precision must be an integer".into(),
))
}
}
} else {
0
};
if val.is_null() {
return Ok(Value::Null);
}
let f = match &val {
Value::Decimal(raw, scale) => {
let divisor = 10f64.powi(*scale as i32);
*raw as f64 / divisor
}
Value::Float(bits) => f64::from_bits(*bits),
Value::TinyInt(v) => *v as f64,
Value::SmallInt(v) => *v as f64,
Value::Int(v) => *v as f64,
Value::BigInt(v) => *v as f64,
_ => {
return Err(DbError::Execution(
"ROUND requires a numeric argument".into(),
))
}
};
let multiplier = 10f64.powi(precision);
let rounded = (f * multiplier).round() / multiplier;
match &val {
Value::Decimal(_raw, scale) => {
let result_raw = (rounded * 10f64.powi(*scale as i32)).round() as i128;
Ok(Value::Decimal(result_raw, *scale))
}
Value::Float(_) => Ok(Value::Float(rounded.to_bits())),
Value::Int(_) => Ok(Value::Int(rounded as i32)),
Value::BigInt(_) => Ok(Value::BigInt(rounded as i64)),
Value::TinyInt(_) => Ok(Value::TinyInt(rounded as u8)),
Value::SmallInt(_) => Ok(Value::SmallInt(rounded as i16)),
Value::Money(_) => Ok(Value::Money((rounded * 10000.0).round() as i128)),
Value::SmallMoney(_) => Ok(Value::SmallMoney((rounded * 10000.0).round() as i64)),
_ => Ok(Value::Float(rounded.to_bits())),
}
}
pub(crate) fn eval_abs(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
if args.len() != 1 {
return Err(DbError::Execution("ABS expects 1 argument".into()));
}
let val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
if val.is_null() {
return Ok(Value::Null);
}
match &val {
Value::TinyInt(v) => Ok(Value::TinyInt(*v)),
Value::SmallInt(v) => Ok(Value::SmallInt(v.abs())),
Value::Int(v) => Ok(Value::Int(v.abs())),
Value::BigInt(v) => Ok(Value::BigInt(v.abs())),
Value::Decimal(raw, scale) => Ok(Value::Decimal(raw.abs(), *scale)),
Value::Float(bits) => Ok(Value::Float((f64::from_bits(*bits).abs()).to_bits())),
Value::Money(v) => Ok(Value::Money(v.abs())),
Value::SmallMoney(v) => Ok(Value::SmallMoney(v.abs())),
_ => {
let f = value_to_f64(&val)?;
Ok(Value::Float(f.abs().to_bits()))
}
}
}
pub(crate) fn eval_power(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
if args.len() != 2 {
return Err(DbError::Execution("POWER expects 2 arguments".into()));
}
let base = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
let exponent = eval_expr(&args[1], row, ctx, catalog, storage, clock)?;
if base.is_null() || exponent.is_null() {
return Ok(Value::Null);
}
let b = value_to_f64(&base)?;
let e = value_to_f64(&exponent)?;
let result = b.powf(e);
Ok(Value::Float(result.to_bits()))
}
pub(crate) fn eval_sqrt(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
if args.len() != 1 {
return Err(DbError::Execution("SQRT expects 1 argument".into()));
}
let val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
if val.is_null() {
return Ok(Value::Null);
}
let f = value_to_f64(&val)?;
let result = f.sqrt();
Ok(Value::Float(result.to_bits()))
}
pub(crate) fn eval_sign(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
if args.len() != 1 {
return Err(DbError::Execution("SIGN expects 1 argument".into()));
}
let val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
if val.is_null() {
return Ok(Value::Null);
}
let f = value_to_f64(&val)?;
let result: f64 = if f > 0.0 {
1.0
} else if f < 0.0 {
-1.0
} else {
0.0
};
Ok(Value::Float(result.to_bits()))
}
pub(crate) fn eval_checksum(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
if args.is_empty() {
return Err(DbError::Execution(
"CHECKSUM requires at least one argument".into(),
));
}
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
for arg in args {
let val = eval_expr(arg, row, ctx, catalog, storage, clock)?;
match val {
Value::Null => {
0i64.hash(&mut hasher);
}
Value::Bit(v) => {
v.hash(&mut hasher);
}
Value::TinyInt(v) => {
v.hash(&mut hasher);
}
Value::SmallInt(v) => {
v.hash(&mut hasher);
}
Value::Int(v) => {
v.hash(&mut hasher);
}
Value::BigInt(v) => {
v.hash(&mut hasher);
}
Value::Float(v) => {
v.hash(&mut hasher);
}
_ => {
val.to_string_value().hash(&mut hasher);
}
}
}
let hash = hasher.finish();
Ok(Value::Int((hash as i64) as i32))
}
pub(crate) fn eval_atn2(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
if args.len() != 2 {
return Err(DbError::Execution("ATN2 expects 2 arguments".into()));
}
let y = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
let x = eval_expr(&args[1], row, ctx, catalog, storage, clock)?;
if y.is_null() || x.is_null() {
return Ok(Value::Null);
}
let yf = value_to_f64(&y)?;
let xf = value_to_f64(&x)?;
Ok(Value::Float(yf.atan2(xf).to_bits()))
}
pub(crate) fn eval_log(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
if args.is_empty() || args.len() > 2 {
return Err(DbError::Execution("LOG expects 1 or 2 arguments".into()));
}
let val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
if val.is_null() {
return Ok(Value::Null);
}
let f = value_to_f64(&val)?;
let result = if args.len() == 2 {
let base_val = eval_expr(&args[1], row, ctx, catalog, storage, clock)?;
if base_val.is_null() {
return Ok(Value::Null);
}
let base = value_to_f64(&base_val)?;
f.log(base)
} else {
f.ln()
};
Ok(Value::Float(result.to_bits()))
}
pub(crate) fn eval_pi(
args: &[Expr],
_row: &[ContextTable],
_ctx: &mut ExecutionContext,
_catalog: &dyn Catalog,
_storage: &dyn Storage,
_clock: &dyn Clock,
) -> Result<Value, DbError> {
if !args.is_empty() {
return Err(DbError::Execution("PI expects no arguments".into()));
}
Ok(Value::Float(std::f64::consts::PI.to_bits()))
}
pub(crate) fn eval_ceiling(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary(args, row, ctx, catalog, storage, clock, "CEILING", |f| {
f.ceil()
})
}
pub(crate) fn eval_floor(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary(args, row, ctx, catalog, storage, clock, "FLOOR", |f| {
f.floor()
})
}
pub(crate) fn eval_square(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary(args, row, ctx, catalog, storage, clock, "SQUARE", |f| f * f)
}
pub(crate) fn eval_exp(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary(args, row, ctx, catalog, storage, clock, "EXP", |f| f.exp())
}
pub(crate) fn eval_log10(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary(args, row, ctx, catalog, storage, clock, "LOG10", |f| {
f.log10()
})
}
pub(crate) fn eval_sin(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "SIN", |f| f.sin())
}
pub(crate) fn eval_cos(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "COS", |f| f.cos())
}
pub(crate) fn eval_tan(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "TAN", |f| f.tan())
}
pub(crate) fn eval_asin(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "ASIN", |f| {
f.asin()
})
}
pub(crate) fn eval_acos(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "ACOS", |f| {
f.acos()
})
}
pub(crate) fn eval_atan(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "ATAN", |f| {
f.atan()
})
}
pub(crate) fn eval_cot(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "COT", |f| {
1.0 / f.tan()
})
}
pub(crate) fn eval_degrees(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "DEGREES", |f| {
f.to_degrees()
})
}
pub(crate) fn eval_radians(
args: &[Expr],
row: &[ContextTable],
ctx: &mut ExecutionContext,
catalog: &dyn Catalog,
storage: &dyn Storage,
clock: &dyn Clock,
) -> Result<Value, DbError> {
eval_math_unary_float(args, row, ctx, catalog, storage, clock, "RADIANS", |f| {
f.to_radians()
})
}
pub(crate) fn eval_rand(
_args: &[Expr],
_row: &[ContextTable],
ctx: &mut ExecutionContext,
_catalog: &dyn Catalog,
_storage: &dyn Storage,
_clock: &dyn Clock,
) -> Result<Value, DbError> {
use super::system::random::deterministic_rand;
let val = deterministic_rand(&mut *ctx.session.random_state);
Ok(Value::Float(val.to_bits()))
}