use vibesql_types::SqlValue;
use crate::{
errors::ExecutorError,
evaluator::casting::{string_to_number, to_i64},
evaluator::operators::is_truthy_string,
};
pub(crate) fn eval_unary_op(
op: &vibesql_ast::UnaryOperator,
val: &SqlValue,
) -> Result<SqlValue, ExecutorError> {
use vibesql_ast::UnaryOperator::*;
match (op, val) {
(Plus, SqlValue::Integer(n)) => Ok(SqlValue::Integer(*n)),
(Plus, SqlValue::Smallint(n)) => Ok(SqlValue::Smallint(*n)),
(Plus, SqlValue::Bigint(n)) => Ok(SqlValue::Bigint(*n)),
(Plus, SqlValue::Float(n)) => Ok(SqlValue::Float(*n)),
(Plus, SqlValue::Real(n)) => Ok(SqlValue::Real(*n)),
(Plus, SqlValue::Double(n)) => Ok(SqlValue::Double(*n)),
(Plus, SqlValue::Numeric(s)) => Ok(SqlValue::Numeric(*s)),
(Minus, SqlValue::Integer(n)) => Ok(SqlValue::Integer(-n)),
(Minus, SqlValue::Smallint(n)) => Ok(SqlValue::Smallint(-n)),
(Minus, SqlValue::Bigint(n)) => Ok(SqlValue::Bigint(-n)),
(Minus, SqlValue::Float(n)) => Ok(SqlValue::Float(-n)),
(Minus, SqlValue::Real(n)) => Ok(SqlValue::Real(-n)),
(Minus, SqlValue::Double(n)) => Ok(SqlValue::Double(-n)),
(Minus, SqlValue::Numeric(f)) => Ok(SqlValue::Numeric(-*f)),
(Plus | Minus, SqlValue::Null) => Ok(SqlValue::Null),
(Plus, SqlValue::Character(s)) => Ok(SqlValue::Character(s.clone())),
(Plus, SqlValue::Varchar(s)) => Ok(SqlValue::Varchar(s.clone())),
(Plus, SqlValue::Blob(b)) => Ok(SqlValue::Blob(b.clone())),
(Minus, SqlValue::Character(s)) | (Minus, SqlValue::Varchar(s)) => {
let (int_val, float_val, is_float) = string_to_number(s);
if is_float {
Ok(SqlValue::Real(-float_val))
} else {
Ok(SqlValue::Integer(-int_val))
}
}
(Minus, SqlValue::Blob(b)) => {
let s = String::from_utf8_lossy(b);
let (int_val, float_val, is_float) = string_to_number(&s);
if is_float {
Ok(SqlValue::Real(-float_val))
} else {
Ok(SqlValue::Integer(-int_val))
}
}
(Not, SqlValue::Null) => Ok(SqlValue::Null), (Not, SqlValue::Boolean(b)) => Ok(SqlValue::Boolean(!b)),
(Not, SqlValue::Integer(n)) => Ok(SqlValue::Boolean(*n == 0)),
(Not, SqlValue::Smallint(n)) => Ok(SqlValue::Boolean(*n == 0)),
(Not, SqlValue::Bigint(n)) => Ok(SqlValue::Boolean(*n == 0)),
(Not, SqlValue::Unsigned(n)) => Ok(SqlValue::Boolean(*n == 0)),
(Not, SqlValue::Float(f)) => Ok(SqlValue::Boolean(!(*f != 0.0))),
(Not, SqlValue::Real(f)) => Ok(SqlValue::Boolean(!(*f != 0.0))),
(Not, SqlValue::Double(f)) => Ok(SqlValue::Boolean(!(*f != 0.0))),
(Not, SqlValue::Numeric(d)) => Ok(SqlValue::Boolean(!(*d != 0.0))),
(Not, SqlValue::Character(s)) | (Not, SqlValue::Varchar(s)) => {
Ok(SqlValue::Boolean(!is_truthy_string(s)))
}
(Not, SqlValue::Date(_)) => Ok(SqlValue::Boolean(false)), (Not, SqlValue::Time(_)) => Ok(SqlValue::Boolean(false)), (Not, SqlValue::Timestamp(_)) => Ok(SqlValue::Boolean(false)),
(Not, SqlValue::Interval(_)) => Ok(SqlValue::Boolean(false)),
(BitwiseNot, SqlValue::Null) => Ok(SqlValue::Null),
(BitwiseNot, val) => Ok(SqlValue::Integer(!to_i64(val)?)),
(Plus, val) => Err(ExecutorError::TypeMismatch {
left: val.clone(),
op: "unary +".to_string(),
right: SqlValue::Null,
}),
(Minus, val) => Err(ExecutorError::TypeMismatch {
left: val.clone(),
op: "unary -".to_string(),
right: SqlValue::Null,
}),
_ => Err(ExecutorError::UnsupportedExpression(format!(
"Unary operator {:?} not supported in this context",
op
))),
}
}
#[cfg(test)]
mod tests {
use vibesql_ast::UnaryOperator;
use super::*;
#[test]
fn test_not_boolean() {
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Boolean(true)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Boolean(false)).unwrap(),
SqlValue::Boolean(true)
);
}
#[test]
fn test_not_null() {
assert_eq!(eval_unary_op(&UnaryOperator::Not, &SqlValue::Null).unwrap(), SqlValue::Null);
}
#[test]
fn test_not_integer() {
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Integer(77)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Integer(-4931)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Integer(0)).unwrap(),
SqlValue::Boolean(true)
);
}
#[test]
fn test_not_float() {
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Float(3.5)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Float(0.0)).unwrap(),
SqlValue::Boolean(true)
);
}
#[test]
fn test_not_varchar() {
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Varchar(arcstr::ArcStr::from("hello")))
.unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Varchar(arcstr::ArcStr::from("")))
.unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Varchar(arcstr::ArcStr::from("0x")))
.unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Varchar(arcstr::ArcStr::from("1x")))
.unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Varchar(arcstr::ArcStr::from("42abc")))
.unwrap(),
SqlValue::Boolean(false)
);
}
#[test]
fn test_not_numeric() {
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Numeric(3.5)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Not, &SqlValue::Numeric(0.0)).unwrap(),
SqlValue::Boolean(true)
);
}
#[test]
fn test_unary_plus_standard_mode() {
assert_eq!(
eval_unary_op(&UnaryOperator::Plus, &SqlValue::Integer(42)).unwrap(),
SqlValue::Integer(42)
);
}
#[test]
fn test_unary_plus_mysql_mode() {
assert_eq!(
eval_unary_op(&UnaryOperator::Plus, &SqlValue::Integer(42)).unwrap(),
SqlValue::Integer(42)
);
}
#[test]
fn test_unary_minus_standard_mode() {
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Integer(42)).unwrap(),
SqlValue::Integer(-42)
);
}
#[test]
fn test_unary_minus_mysql_mode() {
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Integer(42)).unwrap(),
SqlValue::Integer(-42)
);
}
#[test]
fn test_unary_plus_on_text() {
assert_eq!(
eval_unary_op(&UnaryOperator::Plus, &SqlValue::Varchar(arcstr::ArcStr::from("hello")))
.unwrap(),
SqlValue::Varchar(arcstr::ArcStr::from("hello"))
);
assert_eq!(
eval_unary_op(
&UnaryOperator::Plus,
&SqlValue::Character(arcstr::ArcStr::from("world"))
)
.unwrap(),
SqlValue::Character(arcstr::ArcStr::from("world"))
);
assert_eq!(
eval_unary_op(&UnaryOperator::Plus, &SqlValue::Varchar(arcstr::ArcStr::from("")))
.unwrap(),
SqlValue::Varchar(arcstr::ArcStr::from(""))
);
}
#[test]
fn test_unary_minus_on_text() {
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Varchar(arcstr::ArcStr::from(".")))
.unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Varchar(arcstr::ArcStr::from("abc")))
.unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Varchar(arcstr::ArcStr::from("")))
.unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Varchar(arcstr::ArcStr::from("123")))
.unwrap(),
SqlValue::Integer(-123)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Varchar(arcstr::ArcStr::from(" 5 ")))
.unwrap(),
SqlValue::Integer(-5)
);
let result =
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Varchar(arcstr::ArcStr::from("3.14")))
.unwrap();
match result {
SqlValue::Real(v) => assert!((v - (-3.14)).abs() < 0.001, "Expected -3.14, got {}", v),
other => panic!("Expected Real, got {:?}", other),
}
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Character(arcstr::ArcStr::from("42")))
.unwrap(),
SqlValue::Integer(-42)
);
}
#[test]
fn test_unary_plus_on_blob() {
let bytes = vec![0x61u8, 0x62, 0x63];
assert_eq!(
eval_unary_op(&UnaryOperator::Plus, &SqlValue::Blob(bytes.clone())).unwrap(),
SqlValue::Blob(bytes)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Plus, &SqlValue::Blob(vec![])).unwrap(),
SqlValue::Blob(vec![])
);
}
#[test]
fn test_unary_minus_on_blob() {
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Blob(vec![0x61, 0x62, 0x63])).unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
eval_unary_op(&UnaryOperator::Minus, &SqlValue::Blob(vec![])).unwrap(),
SqlValue::Integer(0)
);
}
#[test]
fn unary_minus_on_numeric_blob_returns_integer() {
let blob = SqlValue::Blob(b"123".to_vec());
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Integer(-123));
}
#[test]
fn unary_minus_on_decimal_blob_returns_real() {
let blob = SqlValue::Blob(b".5".to_vec());
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Real(-0.5));
}
#[test]
fn unary_minus_on_exponent_blob_returns_real() {
let blob = SqlValue::Blob(b"1e50".to_vec());
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
match result {
SqlValue::Real(f) => {
assert!(
(f + 1e50).abs() / 1e50 < 1e-12,
"Expected ~-1e50, got {}",
f
);
}
other => panic!("Expected Real, got {:?}", other),
}
}
#[test]
fn unary_minus_on_non_numeric_blob_returns_integer_zero() {
let blob = SqlValue::Blob(b"abc".to_vec());
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Integer(0));
}
#[test]
fn unary_minus_on_empty_blob_returns_integer_zero() {
let blob = SqlValue::Blob(vec![]);
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Integer(0));
}
#[test]
fn unary_minus_on_blob_extracts_numeric_prefix() {
let blob = SqlValue::Blob(b"123abc".to_vec());
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Integer(-123));
}
#[test]
fn unary_minus_on_blob_with_leading_whitespace() {
let blob = SqlValue::Blob(b" 123".to_vec());
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Integer(-123));
}
#[test]
fn unary_minus_on_blob_with_negative_prefix() {
let blob = SqlValue::Blob(b"-123".to_vec());
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Integer(123));
}
#[test]
fn unary_minus_on_blob_with_invalid_utf8() {
let blob = SqlValue::Blob(vec![0xFFu8]);
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Integer(0));
}
#[test]
fn unary_minus_on_blob_with_negative_decimal() {
let blob = SqlValue::Blob(b"-.5".to_vec());
let result = eval_unary_op(&UnaryOperator::Minus, &blob).unwrap();
assert_eq!(result, SqlValue::Real(0.5));
}
}