use vibesql_types::SqlValue;
use super::coerce_numeric_values;
use crate::{
errors::ExecutorError,
evaluator::{coercion::coerce_to_date, functions::datetime::date_add_subtract},
};
pub struct Addition;
impl Addition {
#[inline]
pub fn add(left: &SqlValue, right: &SqlValue) -> Result<SqlValue, ExecutorError> {
use SqlValue::*;
if matches!(left, Null) || matches!(right, Null) {
return Ok(Null);
}
if let (Integer(a), Integer(b)) = (left, right) {
return Ok(a
.checked_add(*b)
.map(Integer)
.unwrap_or_else(|| Double(*a as f64 + *b as f64)));
}
match (left, right) {
(Null, Interval(_)) | (Interval(_), Null) => return Ok(Null),
(Null, Date(_)) | (Date(_), Null) => return Ok(Null),
(Null, Timestamp(_)) | (Timestamp(_), Null) => return Ok(Null),
(Null, Varchar(_))
| (Varchar(_), Null)
| (Null, Character(_))
| (Character(_), Null) => {
if matches!(left, Interval(_)) || matches!(right, Interval(_)) {
return Ok(Null);
}
}
(Date(date), Interval(interval)) => {
return apply_interval_to_date(&date.to_string(), interval, true);
}
(Interval(interval), Date(date)) => {
return apply_interval_to_date(&date.to_string(), interval, true);
}
(Timestamp(ts), Interval(interval)) => {
return apply_interval_to_date(&ts.to_string(), interval, true);
}
(Interval(interval), Timestamp(ts)) => {
return apply_interval_to_date(&ts.to_string(), interval, true);
}
(Varchar(_) | Character(_), Interval(interval)) => {
let date_val = coerce_to_date(left)?;
let date_str = date_val_to_string(&date_val)?;
return apply_interval_to_date(&date_str, interval, true);
}
(Interval(interval), Varchar(_) | Character(_)) => {
let date_val = coerce_to_date(right)?;
let date_str = date_val_to_string(&date_val)?;
return apply_interval_to_date(&date_str, interval, true);
}
_ => {}
}
match coerce_numeric_values(left, right, "+")? {
super::CoercedValues::ExactNumeric(a, b) => Ok(a
.checked_add(b)
.map(Integer)
.unwrap_or_else(|| super::nan_to_null(Double(a as f64 + b as f64)))),
super::CoercedValues::ApproximateNumeric(a, b) => {
Ok(super::nan_to_null(Float((a + b) as f32)))
}
super::CoercedValues::Numeric(a, b) => Ok(super::nan_to_null(Numeric(a + b))),
}
}
}
fn apply_interval_to_date(
date_str: &str,
interval: &vibesql_types::Interval,
is_addition: bool,
) -> Result<SqlValue, ExecutorError> {
let parts: Vec<&str> = interval.value.split_whitespace().collect();
if parts.len() < 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"Invalid interval format: '{}'",
interval.value
)));
}
let amount_str = parts[0];
let unit_str = parts[1];
let amount: i64 = if amount_str.contains('-') {
let parts: Vec<&str> = amount_str.split('-').collect();
parts[0].parse().map_err(|_| {
ExecutorError::UnsupportedFeature(format!("Invalid interval amount: '{}'", amount_str))
})?
} else {
amount_str.parse().map_err(|_| {
ExecutorError::UnsupportedFeature(format!("Invalid interval amount: '{}'", amount_str))
})?
};
let signed_amount = if is_addition { amount } else { -amount };
date_add_subtract(date_str, signed_amount, unit_str, true)
}
fn date_val_to_string(value: &SqlValue) -> Result<String, ExecutorError> {
match value {
SqlValue::Date(d) => Ok(d.to_string()),
SqlValue::Timestamp(ts) => Ok(ts.to_string()),
SqlValue::Null => Ok("NULL".to_string()), val => Err(ExecutorError::UnsupportedFeature(format!(
"Expected DATE or TIMESTAMP, got {:?}",
val
))),
}
}