use vibesql_types::SqlValue;
use crate::errors::ExecutorError;
use crate::evaluator::functions::coercion::{coerce_to_integer, coerce_to_number};
pub fn round(args: &[SqlValue]) -> Result<SqlValue, ExecutorError> {
if args.is_empty() || args.len() > 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"ROUND requires 1 or 2 arguments, got {}",
args.len()
)));
}
let value = &args[0];
let precision = if args.len() == 2 {
match coerce_to_integer(&args[1]) {
Some(p) => {
if p > 30 {
30usize
} else if p < 0 {
0usize
} else {
p as usize
}
}
None => return Ok(SqlValue::Null),
}
} else {
0usize
};
let num = match value {
SqlValue::Null => return Ok(SqlValue::Null),
SqlValue::Integer(n) => *n as f64,
SqlValue::Float(f) => *f as f64,
SqlValue::Double(f) => *f,
SqlValue::Real(f) => *f as f64,
SqlValue::Numeric(n) => *n,
_ => {
match coerce_to_number(value) {
None => return Ok(SqlValue::Null),
Some(n) => n,
}
}
};
let result = round_with_precision(num, precision);
Ok(SqlValue::Double(result))
}
fn round_with_precision(value: f64, precision: usize) -> f64 {
const MAX_ROUNDABLE: f64 = 4503599627370496.0; if value < -MAX_ROUNDABLE || value > MAX_ROUNDABLE {
return value;
}
if precision == 0 {
if value < 0.0 {
(value - 0.5) as i64 as f64
} else {
(value + 0.5) as i64 as f64
}
} else {
let formatted = format!("{:.prec$}", value, prec = precision);
formatted.parse::<f64>().unwrap_or(value)
}
}
pub fn floor(args: &[SqlValue]) -> Result<SqlValue, ExecutorError> {
if args.len() != 1 {
return Err(ExecutorError::UnsupportedFeature(format!(
"FLOOR requires exactly 1 argument, got {}",
args.len()
)));
}
match &args[0] {
SqlValue::Null => return Ok(SqlValue::Null),
SqlValue::Integer(n) => return Ok(SqlValue::Integer(*n)),
SqlValue::Float(f) => return Ok(SqlValue::Float(f.floor())),
SqlValue::Double(f) => return Ok(SqlValue::Double(f.floor())),
SqlValue::Real(f) => return Ok(SqlValue::Real(f.floor())),
SqlValue::Numeric(n) => return Ok(SqlValue::Numeric(n.floor())),
_ => {}
}
match coerce_to_number(&args[0]) {
None => Ok(SqlValue::Null),
Some(n) => Ok(SqlValue::Double(n.floor())),
}
}
pub fn ceil(args: &[SqlValue]) -> Result<SqlValue, ExecutorError> {
if args.len() != 1 {
return Err(ExecutorError::UnsupportedFeature(format!(
"CEIL requires exactly 1 argument, got {}",
args.len()
)));
}
match &args[0] {
SqlValue::Null => return Ok(SqlValue::Null),
SqlValue::Integer(n) => return Ok(SqlValue::Integer(*n)),
SqlValue::Float(f) => return Ok(SqlValue::Float(f.ceil())),
SqlValue::Double(f) => return Ok(SqlValue::Double(f.ceil())),
SqlValue::Real(f) => return Ok(SqlValue::Real(f.ceil())),
SqlValue::Numeric(n) => return Ok(SqlValue::Numeric(n.ceil())),
_ => {}
}
match coerce_to_number(&args[0]) {
None => Ok(SqlValue::Null),
Some(n) => Ok(SqlValue::Double(n.ceil())),
}
}
pub fn truncate(args: &[SqlValue]) -> Result<SqlValue, ExecutorError> {
if args.is_empty() || args.len() > 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"TRUNCATE requires 1 or 2 arguments, got {}",
args.len()
)));
}
let value = &args[0];
let precision = if args.len() == 2 {
match coerce_to_integer(&args[1]) {
Some(p) => p as i32,
None => return Ok(SqlValue::Null),
}
} else {
0
};
match value {
SqlValue::Null => return Ok(SqlValue::Null),
SqlValue::Integer(n) => return Ok(SqlValue::Integer(*n)),
SqlValue::Float(f) => {
let multiplier = 10_f32.powi(precision);
return Ok(SqlValue::Float((f * multiplier).trunc() / multiplier));
}
SqlValue::Double(f) => {
let multiplier = 10_f64.powi(precision);
return Ok(SqlValue::Double((f * multiplier).trunc() / multiplier));
}
SqlValue::Real(f) => {
let multiplier = 10_f64.powi(precision);
return Ok(SqlValue::Real((f * multiplier).trunc() / multiplier));
}
SqlValue::Numeric(n) => {
let multiplier = 10_f64.powi(precision);
return Ok(SqlValue::Numeric((n * multiplier).trunc() / multiplier));
}
_ => {}
}
match coerce_to_number(value) {
None => Ok(SqlValue::Null),
Some(n) => {
let multiplier = 10_f64.powi(precision);
Ok(SqlValue::Double((n * multiplier).trunc() / multiplier))
}
}
}