pub mod equality;
pub mod ordering;
use std::str::FromStr;
use vibesql_types::SqlValue;
use crate::{
errors::ExecutorError,
evaluator::casting::{
boolean_to_i64, is_approximate_numeric, is_exact_numeric, to_f64, to_i64,
},
};
pub(crate) struct ComparisonOps;
impl ComparisonOps {
#[inline]
pub fn equal(left: &SqlValue, right: &SqlValue) -> Result<SqlValue, ExecutorError> {
equality::equal(left, right)
}
#[inline]
pub fn not_equal(left: &SqlValue, right: &SqlValue) -> Result<SqlValue, ExecutorError> {
equality::not_equal(left, right)
}
#[inline]
pub fn less_than(left: &SqlValue, right: &SqlValue) -> Result<SqlValue, ExecutorError> {
ordering::less_than(left, right)
}
#[inline]
pub fn less_than_or_equal(
left: &SqlValue,
right: &SqlValue,
) -> Result<SqlValue, ExecutorError> {
ordering::less_than_or_equal(left, right)
}
#[inline]
pub fn greater_than(left: &SqlValue, right: &SqlValue) -> Result<SqlValue, ExecutorError> {
ordering::greater_than(left, right)
}
#[inline]
pub fn greater_than_or_equal(
left: &SqlValue,
right: &SqlValue,
) -> Result<SqlValue, ExecutorError> {
ordering::greater_than_or_equal(left, right)
}
}
#[inline]
pub(crate) fn compare<F>(
left: &SqlValue,
right: &SqlValue,
predicate: F,
op_str: &str,
) -> Result<SqlValue, ExecutorError>
where
F: FnOnce(std::cmp::Ordering) -> bool,
{
use SqlValue::*;
if matches!(left, Null) || matches!(right, Null) {
return Ok(Null);
}
match (left, right) {
(Boolean(_), right_val)
if is_exact_numeric(right_val)
|| is_approximate_numeric(right_val)
|| matches!(right_val, Numeric(_)) =>
{
let left_i64 = boolean_to_i64(left).unwrap();
if is_exact_numeric(right_val) {
let right_i64 = to_i64(right_val)?;
return Ok(Boolean(predicate(left_i64.cmp(&right_i64))));
}
let left_f64 = left_i64 as f64;
let right_f64 = to_f64(right_val)?;
return Ok(Boolean(predicate(
left_f64.partial_cmp(&right_f64).unwrap_or(std::cmp::Ordering::Equal),
)));
}
(left_val, Boolean(_))
if is_exact_numeric(left_val)
|| is_approximate_numeric(left_val)
|| matches!(left_val, Numeric(_)) =>
{
let right_i64 = boolean_to_i64(right).unwrap();
if is_exact_numeric(left_val) {
let left_i64 = to_i64(left_val)?;
return Ok(Boolean(predicate(left_i64.cmp(&right_i64))));
}
let left_f64 = to_f64(left_val)?;
let right_f64 = right_i64 as f64;
return Ok(Boolean(predicate(
left_f64.partial_cmp(&right_f64).unwrap_or(std::cmp::Ordering::Equal),
)));
}
_ => {} }
match (left, right) {
(Date(date_val), Varchar(s)) => match vibesql_types::Date::from_str(s) {
Ok(parsed_date) => {
return Ok(Boolean(predicate(date_val.cmp(&parsed_date))));
}
Err(_) => {
return Err(ExecutorError::TypeMismatch {
left: left.clone(),
op: op_str.to_string(),
right: right.clone(),
});
}
},
(Varchar(s), Date(date_val)) => match vibesql_types::Date::from_str(s) {
Ok(parsed_date) => {
return Ok(Boolean(predicate(parsed_date.cmp(date_val))));
}
Err(_) => {
return Err(ExecutorError::TypeMismatch {
left: left.clone(),
op: op_str.to_string(),
right: right.clone(),
});
}
},
(Date(date_val), Character(s)) => match vibesql_types::Date::from_str(s) {
Ok(parsed_date) => {
return Ok(Boolean(predicate(date_val.cmp(&parsed_date))));
}
Err(_) => {
return Err(ExecutorError::TypeMismatch {
left: left.clone(),
op: op_str.to_string(),
right: right.clone(),
});
}
},
(Character(s), Date(date_val)) => match vibesql_types::Date::from_str(s) {
Ok(parsed_date) => {
return Ok(Boolean(predicate(parsed_date.cmp(date_val))));
}
Err(_) => {
return Err(ExecutorError::TypeMismatch {
left: left.clone(),
op: op_str.to_string(),
right: right.clone(),
});
}
},
(Timestamp(ts), Varchar(s)) | (Timestamp(ts), Character(s)) => {
return Ok(Boolean(predicate(ts.to_string().as_str().cmp(s.as_str()))));
}
(Varchar(s), Timestamp(ts)) | (Character(s), Timestamp(ts)) => {
return Ok(Boolean(predicate(s.as_str().cmp(ts.to_string().as_str()))));
}
(Time(t), Varchar(s)) | (Time(t), Character(s)) => {
return Ok(Boolean(predicate(t.to_string().as_str().cmp(s.as_str()))));
}
(Varchar(s), Time(t)) | (Character(s), Time(t)) => {
return Ok(Boolean(predicate(s.as_str().cmp(t.to_string().as_str()))));
}
_ => {} }
let is_string = |v: &SqlValue| matches!(v, Varchar(_) | Character(_));
let is_numeric = |v: &SqlValue| {
matches!(
v,
Integer(_) | Smallint(_) | Bigint(_) | Float(_) | Real(_) | Double(_) | Numeric(_)
)
};
let is_blob = |v: &SqlValue| matches!(v, Blob(_));
if is_numeric(left) && is_string(right) {
return Ok(Boolean(predicate(std::cmp::Ordering::Less)));
}
if is_string(left) && is_numeric(right) {
return Ok(Boolean(predicate(std::cmp::Ordering::Greater)));
}
if is_string(left) && is_blob(right) {
return Ok(Boolean(predicate(std::cmp::Ordering::Less)));
}
if is_blob(left) && is_string(right) {
return Ok(Boolean(predicate(std::cmp::Ordering::Greater)));
}
if is_numeric(left) && is_blob(right) {
return Ok(Boolean(predicate(std::cmp::Ordering::Less)));
}
if is_blob(left) && is_numeric(right) {
return Ok(Boolean(predicate(std::cmp::Ordering::Greater)));
}
match (left, right) {
(Blob(a), Blob(b)) => Ok(Boolean(predicate(a.cmp(b)))),
(Integer(a), Integer(b)) => Ok(Boolean(predicate(a.cmp(b)))),
(Varchar(a), Varchar(b)) => Ok(Boolean(predicate(a.cmp(b)))),
(Character(a), Character(b)) => Ok(Boolean(predicate(a.cmp(b)))),
(Character(a), Varchar(b)) | (Varchar(b), Character(a)) => Ok(Boolean(predicate(a.cmp(b)))),
(Date(a), Date(b)) => Ok(Boolean(predicate(a.cmp(b)))),
(Time(a), Time(b)) => Ok(Boolean(predicate(a.cmp(b)))),
(Timestamp(a), Timestamp(b)) => Ok(Boolean(predicate(a.cmp(b)))),
(Boolean(a), Boolean(b)) => Ok(Boolean(predicate(a.cmp(b)))),
(left_val, right_val) if is_exact_numeric(left_val) && is_exact_numeric(right_val) => {
let left_i64 = to_i64(left_val)?;
let right_i64 = to_i64(right_val)?;
Ok(Boolean(predicate(left_i64.cmp(&right_i64))))
}
(left_val, right_val)
if is_approximate_numeric(left_val) && is_approximate_numeric(right_val) =>
{
let left_f64 = to_f64(left_val)?;
let right_f64 = to_f64(right_val)?;
Ok(Boolean(predicate(
left_f64.partial_cmp(&right_f64).unwrap_or(std::cmp::Ordering::Equal),
)))
}
(left_val @ (Float(_) | Real(_) | Double(_)), right_val) if is_exact_numeric(right_val) => {
let left_f64 = to_f64(left_val)?;
let right_i64 = to_i64(right_val)?;
Ok(Boolean(predicate(compare_float_int(left_f64, right_i64))))
}
(left_val, right_val @ (Float(_) | Real(_) | Double(_))) if is_exact_numeric(left_val) => {
let left_i64 = to_i64(left_val)?;
let right_f64 = to_f64(right_val)?;
Ok(Boolean(predicate(compare_int_float(left_i64, right_f64))))
}
(left_val @ Numeric(_), right_val)
if matches!(
right_val,
Integer(_) | Smallint(_) | Bigint(_) | Float(_) | Real(_) | Double(_) | Numeric(_)
) =>
{
let left_f64 = to_f64(left_val)?;
let right_f64 = to_f64(right_val)?;
Ok(Boolean(predicate(
left_f64.partial_cmp(&right_f64).unwrap_or(std::cmp::Ordering::Equal),
)))
}
(left_val, right_val @ Numeric(_))
if matches!(
left_val,
Integer(_) | Smallint(_) | Bigint(_) | Float(_) | Real(_) | Double(_) | Numeric(_)
) =>
{
let left_f64 = to_f64(left_val)?;
let right_f64 = to_f64(right_val)?;
Ok(Boolean(predicate(
left_f64.partial_cmp(&right_f64).unwrap_or(std::cmp::Ordering::Equal),
)))
}
(
Timestamp(_) | Date(_) | Time(_),
Integer(_) | Smallint(_) | Bigint(_) | Float(_) | Real(_) | Double(_) | Numeric(_),
) => {
Ok(Boolean(false))
}
(
Integer(_) | Smallint(_) | Bigint(_) | Float(_) | Real(_) | Double(_) | Numeric(_),
Timestamp(_) | Date(_) | Time(_),
) => {
Ok(Boolean(false))
}
_ => Err(ExecutorError::TypeMismatch {
left: left.clone(),
op: op_str.to_string(),
right: right.clone(),
}),
}
}
#[inline]
fn compare_int_float(int_val: i64, float_val: f64) -> std::cmp::Ordering {
if float_val.is_nan() {
return std::cmp::Ordering::Equal;
}
if float_val.is_infinite() {
return if float_val.is_sign_positive() {
std::cmp::Ordering::Less } else {
std::cmp::Ordering::Greater };
}
const EXACT_INT_MAX: f64 = 9007199254740992.0;
const EXACT_INT_MIN: f64 = -9007199254740992.0;
if float_val >= EXACT_INT_MIN && float_val <= EXACT_INT_MAX && float_val.fract() == 0.0 {
let float_as_int = float_val as i64;
return int_val.cmp(&float_as_int);
}
if float_val.fract() != 0.0 {
let int_as_float = int_val as f64;
return int_as_float.partial_cmp(&float_val).unwrap_or(std::cmp::Ordering::Equal);
}
const I64_MAX_PLUS_ONE_AS_F64: f64 = 9223372036854775808.0_f64;
if float_val >= I64_MAX_PLUS_ONE_AS_F64 {
return std::cmp::Ordering::Less; }
const I64_MIN_AS_F64: f64 = -9223372036854775808.0_f64;
if float_val < I64_MIN_AS_F64 {
return std::cmp::Ordering::Greater; }
if float_val == I64_MIN_AS_F64 {
return int_val.cmp(&i64::MIN);
}
let int_as_float = int_val as f64;
if int_as_float == float_val {
let round_trip = int_as_float as i64;
if round_trip == int_val {
return std::cmp::Ordering::Equal;
}
if round_trip > int_val {
return std::cmp::Ordering::Less;
} else {
return std::cmp::Ordering::Greater;
}
}
int_as_float.partial_cmp(&float_val).unwrap_or(std::cmp::Ordering::Equal)
}
#[inline]
fn compare_float_int(float_val: f64, int_val: i64) -> std::cmp::Ordering {
compare_int_float(int_val, float_val).reverse()
}