use vibesql_types::SqlValue;
use crate::errors::ExecutorError;
pub fn is_truthy_string(s: &str) -> bool {
parse_leading_numeric(s) != 0.0
}
pub fn is_truthy(value: &SqlValue) -> bool {
use SqlValue::*;
match value {
Boolean(b) => *b,
Null => false, Integer(n) => *n != 0,
Smallint(n) => *n != 0,
Bigint(n) => *n != 0,
Unsigned(n) => *n != 0,
Float(f) => *f != 0.0,
Real(f) => *f != 0.0,
Double(f) => *f != 0.0,
Numeric(f) => *f != 0.0,
Varchar(s) | Character(s) => is_truthy_string(s),
_ => false,
}
}
fn parse_leading_numeric(s: &str) -> f64 {
let s = s.trim_start();
if s.is_empty() {
return 0.0;
}
let mut chars = s.chars().peekable();
let mut result = String::new();
if let Some(&c) = chars.peek() {
if c == '+' || c == '-' {
result.push(c);
chars.next();
}
}
let mut has_digits = false;
let mut has_dot = false;
while let Some(&c) = chars.peek() {
if c.is_ascii_digit() {
result.push(c);
has_digits = true;
chars.next();
} else if c == '.' && !has_dot {
result.push(c);
has_dot = true;
chars.next();
} else {
break;
}
}
if !has_digits {
return 0.0;
}
result.parse::<f64>().unwrap_or(0.0)
}
fn to_boolean(value: &SqlValue) -> Result<Option<bool>, ExecutorError> {
use SqlValue::*;
match value {
Boolean(b) => Ok(Some(*b)),
Null => Ok(None),
Integer(n) => Ok(Some(*n != 0)),
Smallint(n) => Ok(Some(*n != 0)),
Bigint(n) => Ok(Some(*n != 0)),
Unsigned(n) => Ok(Some(*n != 0)),
Float(f) => Ok(Some(*f != 0.0)),
Real(f) => Ok(Some(*f != 0.0)),
Double(f) => Ok(Some(*f != 0.0)),
Numeric(f) => Ok(Some(*f != 0.0)),
Varchar(s) | Character(s) => {
let trimmed = s.trim();
let numeric_value = parse_leading_numeric(trimmed);
Ok(Some(numeric_value != 0.0))
}
_ => Err(ExecutorError::TypeMismatch {
left: value.clone(),
op: "boolean context".to_string(),
right: SqlValue::Null,
}),
}
}
pub(crate) struct LogicalOps;
impl LogicalOps {
#[inline]
pub fn and(left: &SqlValue, right: &SqlValue) -> Result<SqlValue, ExecutorError> {
use SqlValue::*;
let left_bool = to_boolean(left)?;
let right_bool = to_boolean(right)?;
match (left_bool, right_bool) {
(Some(a), Some(b)) => Ok(Boolean(a && b)),
(Some(false), None) | (None, Some(false)) => Ok(Boolean(false)),
(Some(true), None) | (None, Some(true)) => Ok(Null),
(None, None) => Ok(Null),
}
}
#[inline]
pub fn or(left: &SqlValue, right: &SqlValue) -> Result<SqlValue, ExecutorError> {
use SqlValue::*;
let left_bool = to_boolean(left)?;
let right_bool = to_boolean(right)?;
match (left_bool, right_bool) {
(Some(a), Some(b)) => Ok(Boolean(a || b)),
(Some(true), None) | (None, Some(true)) => Ok(Boolean(true)),
(Some(false), None) | (None, Some(false)) => Ok(Null),
(None, None) => Ok(Null),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_and_operator() {
assert_eq!(
LogicalOps::and(&SqlValue::Boolean(true), &SqlValue::Boolean(true)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::and(&SqlValue::Boolean(true), &SqlValue::Boolean(false)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::and(&SqlValue::Boolean(false), &SqlValue::Boolean(false)).unwrap(),
SqlValue::Boolean(false)
);
}
#[test]
fn test_or_operator() {
assert_eq!(
LogicalOps::or(&SqlValue::Boolean(true), &SqlValue::Boolean(true)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::or(&SqlValue::Boolean(true), &SqlValue::Boolean(false)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::or(&SqlValue::Boolean(false), &SqlValue::Boolean(false)).unwrap(),
SqlValue::Boolean(false)
);
}
#[test]
fn test_strings_in_boolean_context() {
let result = LogicalOps::and(
&SqlValue::Varchar(arcstr::ArcStr::from("hello")),
&SqlValue::Boolean(true),
);
assert_eq!(result.unwrap(), SqlValue::Boolean(false));
let result = LogicalOps::or(
&SqlValue::Boolean(true),
&SqlValue::Varchar(arcstr::ArcStr::from("world")),
);
assert_eq!(result.unwrap(), SqlValue::Boolean(true));
let result = LogicalOps::and(
&SqlValue::Varchar(arcstr::ArcStr::from("1abc")),
&SqlValue::Boolean(true),
);
assert_eq!(result.unwrap(), SqlValue::Boolean(true));
let result = LogicalOps::and(
&SqlValue::Varchar(arcstr::ArcStr::from("0")),
&SqlValue::Boolean(true),
);
assert_eq!(result.unwrap(), SqlValue::Boolean(false));
}
#[test]
fn test_and_with_integers() {
assert_eq!(
LogicalOps::and(&SqlValue::Integer(1), &SqlValue::Integer(1)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::and(&SqlValue::Integer(1), &SqlValue::Integer(0)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::and(&SqlValue::Integer(0), &SqlValue::Integer(1)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::and(&SqlValue::Integer(0), &SqlValue::Integer(0)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::and(&SqlValue::Integer(42), &SqlValue::Integer(-7)).unwrap(),
SqlValue::Boolean(true)
);
}
#[test]
fn test_or_with_integers() {
assert_eq!(
LogicalOps::or(&SqlValue::Integer(1), &SqlValue::Integer(0)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::or(&SqlValue::Integer(0), &SqlValue::Integer(1)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::or(&SqlValue::Integer(0), &SqlValue::Integer(0)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::or(&SqlValue::Integer(1), &SqlValue::Integer(1)).unwrap(),
SqlValue::Boolean(true)
);
}
#[test]
fn test_and_with_floats() {
assert_eq!(
LogicalOps::and(&SqlValue::Float(1.5), &SqlValue::Float(0.0)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::and(&SqlValue::Float(1.5), &SqlValue::Float(2.5)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::and(&SqlValue::Float(0.0), &SqlValue::Float(0.0)).unwrap(),
SqlValue::Boolean(false)
);
}
#[test]
fn test_or_with_floats() {
assert_eq!(
LogicalOps::or(&SqlValue::Float(1.5), &SqlValue::Float(0.0)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::or(&SqlValue::Float(0.0), &SqlValue::Float(0.0)).unwrap(),
SqlValue::Boolean(false)
);
}
#[test]
fn test_and_with_numeric() {
assert_eq!(
LogicalOps::and(&SqlValue::Numeric(4.3), &SqlValue::Numeric(2.4)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::and(&SqlValue::Numeric(0.0), &SqlValue::Numeric(2.4)).unwrap(),
SqlValue::Boolean(false)
);
}
#[test]
fn test_or_with_numeric() {
assert_eq!(
LogicalOps::or(&SqlValue::Numeric(4.3), &SqlValue::Numeric(0.0)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::or(&SqlValue::Numeric(0.0), &SqlValue::Numeric(0.0)).unwrap(),
SqlValue::Boolean(false)
);
}
#[test]
fn test_mixed_numeric_and_boolean() {
assert_eq!(
LogicalOps::and(&SqlValue::Integer(1), &SqlValue::Boolean(true)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::and(&SqlValue::Integer(0), &SqlValue::Boolean(true)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::or(&SqlValue::Float(0.0), &SqlValue::Boolean(false)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::or(&SqlValue::Float(1.5), &SqlValue::Boolean(false)).unwrap(),
SqlValue::Boolean(true)
);
}
#[test]
fn test_numeric_with_null() {
assert_eq!(
LogicalOps::and(&SqlValue::Integer(0), &SqlValue::Null).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::and(&SqlValue::Integer(1), &SqlValue::Null).unwrap(),
SqlValue::Null
);
assert_eq!(
LogicalOps::or(&SqlValue::Integer(1), &SqlValue::Null).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(LogicalOps::or(&SqlValue::Integer(0), &SqlValue::Null).unwrap(), SqlValue::Null);
}
#[test]
fn test_and_with_null() {
assert_eq!(
LogicalOps::and(&SqlValue::Boolean(false), &SqlValue::Null).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::and(&SqlValue::Null, &SqlValue::Boolean(false)).unwrap(),
SqlValue::Boolean(false)
);
assert_eq!(
LogicalOps::and(&SqlValue::Boolean(true), &SqlValue::Null).unwrap(),
SqlValue::Null
);
assert_eq!(
LogicalOps::and(&SqlValue::Null, &SqlValue::Boolean(true)).unwrap(),
SqlValue::Null
);
assert_eq!(LogicalOps::and(&SqlValue::Null, &SqlValue::Null).unwrap(), SqlValue::Null);
}
#[test]
fn test_or_with_null() {
assert_eq!(
LogicalOps::or(&SqlValue::Boolean(true), &SqlValue::Null).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::or(&SqlValue::Null, &SqlValue::Boolean(true)).unwrap(),
SqlValue::Boolean(true)
);
assert_eq!(
LogicalOps::or(&SqlValue::Boolean(false), &SqlValue::Null).unwrap(),
SqlValue::Null
);
assert_eq!(
LogicalOps::or(&SqlValue::Null, &SqlValue::Boolean(false)).unwrap(),
SqlValue::Null
);
assert_eq!(LogicalOps::or(&SqlValue::Null, &SqlValue::Null).unwrap(), SqlValue::Null);
}
}