use vibesql_ast::{BinaryOperator, Expression};
use vibesql_types::SqlValue;
use crate::schema::CombinedSchema;
#[derive(Debug)]
pub enum CompiledPredicate {
Equals { col_idx: usize, value: SqlValue },
NotEquals { col_idx: usize, value: SqlValue },
Range { col_idx: usize, op: RangeOp, value: SqlValue },
IsNull { col_idx: usize },
IsNotNull { col_idx: usize },
And(Box<CompiledPredicate>, Box<CompiledPredicate>),
Or(Box<CompiledPredicate>, Box<CompiledPredicate>),
Conjunction(Vec<CompiledPredicate>),
Disjunction(Vec<CompiledPredicate>),
#[allow(dead_code)]
Complex(Expression),
}
#[derive(Debug, Clone, Copy)]
pub enum RangeOp {
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
}
impl CompiledPredicate {
pub fn compile(expr: &Expression, schema: &CombinedSchema) -> Self {
Self::try_compile(expr, schema).unwrap_or_else(|| CompiledPredicate::Complex(expr.clone()))
}
fn try_compile(expr: &Expression, schema: &CombinedSchema) -> Option<Self> {
match expr {
Expression::BinaryOp { left, op, right } => {
Self::try_compile_binary_op(left, op, right, schema)
}
Expression::Conjunction(children) => {
let compiled: Vec<_> =
children.iter().filter_map(|child| Self::try_compile(child, schema)).collect();
if compiled.len() != children.len() {
return None;
}
if compiled.iter().any(|c| matches!(c, CompiledPredicate::Complex(_))) {
return None;
}
Some(CompiledPredicate::Conjunction(compiled))
}
Expression::Disjunction(children) => {
let compiled: Vec<_> =
children.iter().filter_map(|child| Self::try_compile(child, schema)).collect();
if compiled.len() != children.len() {
return None;
}
if compiled.iter().any(|c| matches!(c, CompiledPredicate::Complex(_))) {
return None;
}
Some(CompiledPredicate::Disjunction(compiled))
}
Expression::IsNull { expr, negated } => {
if let Expression::ColumnRef(col_id) = expr.as_ref() {
let col_idx = schema
.get_column_index(col_id.table_canonical(), col_id.column_canonical())?;
if *negated {
Some(CompiledPredicate::IsNotNull { col_idx })
} else {
Some(CompiledPredicate::IsNull { col_idx })
}
} else {
None
}
}
Expression::Literal(SqlValue::Boolean(true)) => {
None
}
Expression::Literal(SqlValue::Boolean(false)) => {
None
}
_ => None,
}
}
fn try_compile_binary_op(
left: &Expression,
op: &BinaryOperator,
right: &Expression,
schema: &CombinedSchema,
) -> Option<Self> {
match op {
BinaryOperator::And => {
let left_compiled = Self::try_compile(left, schema)?;
let right_compiled = Self::try_compile(right, schema)?;
if matches!(left_compiled, CompiledPredicate::Complex(_))
|| matches!(right_compiled, CompiledPredicate::Complex(_))
{
return None;
}
Some(CompiledPredicate::And(Box::new(left_compiled), Box::new(right_compiled)))
}
BinaryOperator::Or => {
let left_compiled = Self::try_compile(left, schema)?;
let right_compiled = Self::try_compile(right, schema)?;
if matches!(left_compiled, CompiledPredicate::Complex(_))
|| matches!(right_compiled, CompiledPredicate::Complex(_))
{
return None;
}
Some(CompiledPredicate::Or(Box::new(left_compiled), Box::new(right_compiled)))
}
_ => Self::try_compile_comparison(left, op, right, schema),
}
}
fn try_compile_comparison(
left: &Expression,
op: &BinaryOperator,
right: &Expression,
schema: &CombinedSchema,
) -> Option<Self> {
if let (Expression::ColumnRef(col_id), Expression::Literal(value)) = (left, right) {
let col_idx =
schema.get_column_index(col_id.table_canonical(), col_id.column_canonical())?;
if !Self::literal_supported_for_column(schema, col_idx, value) {
return None;
}
return Self::compile_comparison_with_idx(col_idx, op, value.clone(), false);
}
if let (Expression::Literal(value), Expression::ColumnRef(col_id)) = (left, right) {
let col_idx =
schema.get_column_index(col_id.table_canonical(), col_id.column_canonical())?;
if !Self::literal_supported_for_column(schema, col_idx, value) {
return None;
}
return Self::compile_comparison_with_idx(col_idx, op, value.clone(), true);
}
None
}
fn literal_supported_for_column(
schema: &CombinedSchema,
col_idx: usize,
value: &SqlValue,
) -> bool {
use std::str::FromStr;
use vibesql_types::DataType;
let col_type = schema.get_column_type_by_index(col_idx);
let is_string_col = |t: &DataType| {
matches!(
t,
DataType::Character { .. }
| DataType::Varchar { .. }
| DataType::CharacterLargeObject
| DataType::Name
)
};
match value {
SqlValue::Varchar(s) | SqlValue::Character(s) => match col_type {
Some(DataType::Date) => vibesql_types::Date::from_str(s).is_ok(),
Some(DataType::Timestamp { .. }) | Some(DataType::Time { .. }) => {
let trimmed = s.trim();
trimmed.parse::<i64>().is_err() && trimmed.parse::<f64>().is_err()
}
_ => true,
},
SqlValue::Timestamp(_) => match col_type {
Some(DataType::Timestamp { .. }) | None => true,
Some(t) => is_string_col(t),
},
SqlValue::Time(_) => match col_type {
Some(DataType::Time { .. }) | None => true,
Some(t) => is_string_col(t),
},
SqlValue::Date(_) => match col_type {
Some(DataType::Date) | None => true,
Some(t) => is_string_col(t),
},
_ => true,
}
}
fn compile_comparison_with_idx(
col_idx: usize,
op: &BinaryOperator,
value: SqlValue,
reversed: bool,
) -> Option<Self> {
match op {
BinaryOperator::Equal => Some(CompiledPredicate::Equals { col_idx, value }),
BinaryOperator::NotEqual => Some(CompiledPredicate::NotEquals { col_idx, value }),
BinaryOperator::LessThan => {
if reversed {
Some(CompiledPredicate::Range { col_idx, op: RangeOp::GreaterThan, value })
} else {
Some(CompiledPredicate::Range { col_idx, op: RangeOp::LessThan, value })
}
}
BinaryOperator::LessThanOrEqual => {
if reversed {
Some(CompiledPredicate::Range {
col_idx,
op: RangeOp::GreaterThanOrEqual,
value,
})
} else {
Some(CompiledPredicate::Range { col_idx, op: RangeOp::LessThanOrEqual, value })
}
}
BinaryOperator::GreaterThan => {
if reversed {
Some(CompiledPredicate::Range { col_idx, op: RangeOp::LessThan, value })
} else {
Some(CompiledPredicate::Range { col_idx, op: RangeOp::GreaterThan, value })
}
}
BinaryOperator::GreaterThanOrEqual => {
if reversed {
Some(CompiledPredicate::Range { col_idx, op: RangeOp::LessThanOrEqual, value })
} else {
Some(CompiledPredicate::Range {
col_idx,
op: RangeOp::GreaterThanOrEqual,
value,
})
}
}
_ => None,
}
}
#[inline]
pub fn is_fully_compiled(&self) -> bool {
match self {
CompiledPredicate::Complex(_) => false,
CompiledPredicate::And(left, right) | CompiledPredicate::Or(left, right) => {
left.is_fully_compiled() && right.is_fully_compiled()
}
CompiledPredicate::Conjunction(children) | CompiledPredicate::Disjunction(children) => {
children.iter().all(|c| c.is_fully_compiled())
}
_ => true,
}
}
#[inline]
pub fn evaluate(&self, row: &vibesql_storage::Row) -> Option<bool> {
match self {
CompiledPredicate::Equals { col_idx, value } => {
let row_value = row.get(*col_idx)?;
Some(Self::values_equal(row_value, value))
}
CompiledPredicate::NotEquals { col_idx, value } => {
let row_value = row.get(*col_idx)?;
if matches!(row_value, SqlValue::Null) || matches!(value, SqlValue::Null) {
return None;
}
Some(!Self::values_equal(row_value, value))
}
CompiledPredicate::Range { col_idx, op, value } => {
let row_value = row.get(*col_idx)?;
Self::compare_range(row_value, *op, value)
}
CompiledPredicate::IsNull { col_idx } => {
let row_value = row.get(*col_idx)?;
Some(matches!(row_value, SqlValue::Null))
}
CompiledPredicate::IsNotNull { col_idx } => {
let row_value = row.get(*col_idx)?;
Some(!matches!(row_value, SqlValue::Null))
}
CompiledPredicate::And(left, right) => {
let left_result = left.evaluate(row);
if left_result == Some(false) {
return Some(false);
}
let right_result = right.evaluate(row);
match (left_result, right_result) {
(Some(true), Some(true)) => Some(true),
(Some(false), _) | (_, Some(false)) => Some(false),
_ => None, }
}
CompiledPredicate::Or(left, right) => {
let left_result = left.evaluate(row);
if left_result == Some(true) {
return Some(true);
}
let right_result = right.evaluate(row);
match (left_result, right_result) {
(Some(true), _) | (_, Some(true)) => Some(true),
(Some(false), Some(false)) => Some(false),
_ => None, }
}
CompiledPredicate::Conjunction(children) => {
let mut has_null = false;
for child in children.iter() {
match child.evaluate(row) {
Some(false) => return Some(false), Some(true) => {}
None => has_null = true,
}
}
if has_null {
None
} else {
Some(true)
}
}
CompiledPredicate::Disjunction(children) => {
let mut has_null = false;
for child in children.iter() {
match child.evaluate(row) {
Some(true) => return Some(true), Some(false) => {}
None => has_null = true,
}
}
if has_null {
None
} else {
Some(false)
}
}
CompiledPredicate::Complex(_) => {
None
}
}
}
#[inline]
fn values_equal(a: &SqlValue, b: &SqlValue) -> bool {
if matches!(a, SqlValue::Null) || matches!(b, SqlValue::Null) {
return false;
}
match (a, b) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => x == y,
(SqlValue::Bigint(x), SqlValue::Bigint(y)) => x == y,
(SqlValue::Varchar(x), SqlValue::Varchar(y)) => x == y,
(SqlValue::Boolean(x), SqlValue::Boolean(y)) => x == y,
(SqlValue::Integer(x), SqlValue::Bigint(y)) => x == y,
(SqlValue::Bigint(x), SqlValue::Integer(y)) => x == y,
(SqlValue::Integer(x), SqlValue::Smallint(y)) => *x == i64::from(*y),
(SqlValue::Smallint(x), SqlValue::Integer(y)) => i64::from(*x) == *y,
(SqlValue::Bigint(x), SqlValue::Smallint(y)) => *x == i64::from(*y),
(SqlValue::Smallint(x), SqlValue::Bigint(y)) => i64::from(*x) == *y,
(SqlValue::Float(x), SqlValue::Float(y)) => x == y,
(SqlValue::Double(x), SqlValue::Double(y)) => x == y,
(SqlValue::Real(x), SqlValue::Real(y)) => x == y,
(SqlValue::Numeric(x), SqlValue::Numeric(y)) => x == y,
(SqlValue::Float(x), SqlValue::Numeric(y)) => f64::from(*x) == *y,
(SqlValue::Numeric(x), SqlValue::Float(y)) => *x == f64::from(*y),
(SqlValue::Float(x), SqlValue::Double(y)) => f64::from(*x) == *y,
(SqlValue::Double(x), SqlValue::Float(y)) => *x == f64::from(*y),
(SqlValue::Float(x), SqlValue::Real(y)) => f64::from(*x) == *y,
(SqlValue::Real(x), SqlValue::Float(y)) => *x == f64::from(*y),
(SqlValue::Double(x), SqlValue::Numeric(y)) => x == y,
(SqlValue::Numeric(x), SqlValue::Double(y)) => x == y,
(SqlValue::Double(x), SqlValue::Real(y)) => x == y,
(SqlValue::Real(x), SqlValue::Double(y)) => x == y,
(SqlValue::Real(x), SqlValue::Numeric(y)) => x == y,
(SqlValue::Numeric(x), SqlValue::Real(y)) => x == y,
(SqlValue::Integer(x), SqlValue::Float(y)) => (*x as f64) == f64::from(*y),
(SqlValue::Float(x), SqlValue::Integer(y)) => f64::from(*x) == (*y as f64),
(SqlValue::Integer(x), SqlValue::Double(y)) => (*x as f64) == *y,
(SqlValue::Double(x), SqlValue::Integer(y)) => *x == (*y as f64),
(SqlValue::Integer(x), SqlValue::Numeric(y)) => (*x as f64) == *y,
(SqlValue::Numeric(x), SqlValue::Integer(y)) => *x == (*y as f64),
(SqlValue::Integer(x), SqlValue::Real(y)) => (*x as f64) == f64::from(*y),
(SqlValue::Real(x), SqlValue::Integer(y)) => f64::from(*x) == (*y as f64),
(SqlValue::Integer(x), SqlValue::Varchar(s))
| (SqlValue::Integer(x), SqlValue::Character(s)) => {
s.trim().parse::<i64>().map(|y| *x == y).unwrap_or(false)
}
(SqlValue::Varchar(s), SqlValue::Integer(y))
| (SqlValue::Character(s), SqlValue::Integer(y)) => {
s.trim().parse::<i64>().map(|x| x == *y).unwrap_or(false)
}
(SqlValue::Bigint(x), SqlValue::Varchar(s))
| (SqlValue::Bigint(x), SqlValue::Character(s)) => {
s.trim().parse::<i64>().map(|y| *x == y).unwrap_or(false)
}
(SqlValue::Varchar(s), SqlValue::Bigint(y))
| (SqlValue::Character(s), SqlValue::Bigint(y)) => {
s.trim().parse::<i64>().map(|x| x == *y).unwrap_or(false)
}
(SqlValue::Smallint(x), SqlValue::Varchar(s))
| (SqlValue::Smallint(x), SqlValue::Character(s)) => {
s.trim().parse::<i16>().map(|y| *x == y).unwrap_or(false)
}
(SqlValue::Varchar(s), SqlValue::Smallint(y))
| (SqlValue::Character(s), SqlValue::Smallint(y)) => {
s.trim().parse::<i16>().map(|x| x == *y).unwrap_or(false)
}
(SqlValue::Float(x), SqlValue::Varchar(s))
| (SqlValue::Float(x), SqlValue::Character(s)) => {
s.trim().parse::<f64>().map(|y| f64::from(*x) == y).unwrap_or(false)
}
(SqlValue::Varchar(s), SqlValue::Float(y))
| (SqlValue::Character(s), SqlValue::Float(y)) => {
s.trim().parse::<f64>().map(|x| x == f64::from(*y)).unwrap_or(false)
}
(SqlValue::Double(x), SqlValue::Varchar(s))
| (SqlValue::Double(x), SqlValue::Character(s)) => {
s.trim().parse::<f64>().map(|y| *x == y).unwrap_or(false)
}
(SqlValue::Varchar(s), SqlValue::Double(y))
| (SqlValue::Character(s), SqlValue::Double(y)) => {
s.trim().parse::<f64>().map(|x| x == *y).unwrap_or(false)
}
(SqlValue::Real(x), SqlValue::Varchar(s))
| (SqlValue::Real(x), SqlValue::Character(s)) => {
s.trim().parse::<f64>().map(|y| *x == y).unwrap_or(false)
}
(SqlValue::Varchar(s), SqlValue::Real(y))
| (SqlValue::Character(s), SqlValue::Real(y)) => {
s.trim().parse::<f64>().map(|x| x == *y).unwrap_or(false)
}
(SqlValue::Numeric(x), SqlValue::Varchar(s))
| (SqlValue::Numeric(x), SqlValue::Character(s)) => {
s.trim().parse::<f64>().map(|y| *x == y).unwrap_or(false)
}
(SqlValue::Varchar(s), SqlValue::Numeric(y))
| (SqlValue::Character(s), SqlValue::Numeric(y)) => {
s.trim().parse::<f64>().map(|x| x == *y).unwrap_or(false)
}
(SqlValue::Timestamp(x), SqlValue::Varchar(s))
| (SqlValue::Timestamp(x), SqlValue::Character(s)) => x.to_string() == s.as_str(),
(SqlValue::Varchar(s), SqlValue::Timestamp(y))
| (SqlValue::Character(s), SqlValue::Timestamp(y)) => y.to_string() == s.as_str(),
(SqlValue::Time(x), SqlValue::Varchar(s))
| (SqlValue::Time(x), SqlValue::Character(s)) => x.to_string() == s.as_str(),
(SqlValue::Varchar(s), SqlValue::Time(y))
| (SqlValue::Character(s), SqlValue::Time(y)) => y.to_string() == s.as_str(),
(SqlValue::Date(x), SqlValue::Varchar(s))
| (SqlValue::Date(x), SqlValue::Character(s)) => {
use std::str::FromStr;
vibesql_types::Date::from_str(s).map(|d| *x == d).unwrap_or(false)
}
(SqlValue::Varchar(s), SqlValue::Date(y))
| (SqlValue::Character(s), SqlValue::Date(y)) => {
use std::str::FromStr;
vibesql_types::Date::from_str(s).map(|d| d == *y).unwrap_or(false)
}
_ => a == b,
}
}
#[inline]
fn compare_range(row_value: &SqlValue, op: RangeOp, literal: &SqlValue) -> Option<bool> {
if matches!(row_value, SqlValue::Null) || matches!(literal, SqlValue::Null) {
return None;
}
match (row_value, literal) {
(SqlValue::Integer(x), SqlValue::Integer(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Bigint(x), SqlValue::Bigint(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Smallint(x), SqlValue::Smallint(y)) => {
Some(Self::apply_range_op(*x, op, *y))
}
(SqlValue::Integer(x), SqlValue::Smallint(y)) => {
Some(Self::apply_range_op(*x, op, i64::from(*y)))
}
(SqlValue::Smallint(x), SqlValue::Integer(y)) => {
Some(Self::apply_range_op(i64::from(*x), op, *y))
}
(SqlValue::Bigint(x), SqlValue::Smallint(y)) => {
Some(Self::apply_range_op(*x, op, i64::from(*y)))
}
(SqlValue::Smallint(x), SqlValue::Bigint(y)) => {
Some(Self::apply_range_op(i64::from(*x), op, *y))
}
(SqlValue::Integer(x), SqlValue::Bigint(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Bigint(x), SqlValue::Integer(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Varchar(x), SqlValue::Varchar(y)) => {
Some(Self::apply_range_op(&**x, op, &**y))
}
(SqlValue::Float(x), SqlValue::Float(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Double(x), SqlValue::Double(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Real(x), SqlValue::Real(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Numeric(x), SqlValue::Numeric(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Float(x), SqlValue::Numeric(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y))
}
(SqlValue::Numeric(x), SqlValue::Float(y)) => {
Some(Self::apply_range_op(*x, op, f64::from(*y)))
}
(SqlValue::Float(x), SqlValue::Double(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y))
}
(SqlValue::Double(x), SqlValue::Float(y)) => {
Some(Self::apply_range_op(*x, op, f64::from(*y)))
}
(SqlValue::Float(x), SqlValue::Real(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y))
}
(SqlValue::Real(x), SqlValue::Float(y)) => {
Some(Self::apply_range_op(*x, op, f64::from(*y)))
}
(SqlValue::Double(x), SqlValue::Numeric(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Numeric(x), SqlValue::Double(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Double(x), SqlValue::Real(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Real(x), SqlValue::Double(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Real(x), SqlValue::Numeric(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Numeric(x), SqlValue::Real(y)) => Some(Self::apply_range_op(*x, op, *y)),
(SqlValue::Float(x), SqlValue::Integer(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y as f64))
}
(SqlValue::Integer(x), SqlValue::Float(y)) => {
Some(Self::apply_range_op(*x as f64, op, f64::from(*y)))
}
(SqlValue::Double(x), SqlValue::Integer(y)) => {
Some(Self::apply_range_op(*x, op, *y as f64))
}
(SqlValue::Integer(x), SqlValue::Double(y)) => {
Some(Self::apply_range_op(*x as f64, op, *y))
}
(SqlValue::Real(x), SqlValue::Integer(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y as f64))
}
(SqlValue::Integer(x), SqlValue::Real(y)) => {
Some(Self::apply_range_op(*x as f64, op, f64::from(*y)))
}
(SqlValue::Numeric(x), SqlValue::Integer(y)) => {
Some(Self::apply_range_op(*x, op, *y as f64))
}
(SqlValue::Integer(x), SqlValue::Numeric(y)) => {
Some(Self::apply_range_op(*x as f64, op, *y))
}
(SqlValue::Float(x), SqlValue::Bigint(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y as f64))
}
(SqlValue::Bigint(x), SqlValue::Float(y)) => {
Some(Self::apply_range_op(*x as f64, op, f64::from(*y)))
}
(SqlValue::Double(x), SqlValue::Bigint(y)) => {
Some(Self::apply_range_op(*x, op, *y as f64))
}
(SqlValue::Bigint(x), SqlValue::Double(y)) => {
Some(Self::apply_range_op(*x as f64, op, *y))
}
(SqlValue::Real(x), SqlValue::Bigint(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y as f64))
}
(SqlValue::Bigint(x), SqlValue::Real(y)) => {
Some(Self::apply_range_op(*x as f64, op, f64::from(*y)))
}
(SqlValue::Numeric(x), SqlValue::Bigint(y)) => {
Some(Self::apply_range_op(*x, op, *y as f64))
}
(SqlValue::Bigint(x), SqlValue::Numeric(y)) => {
Some(Self::apply_range_op(*x as f64, op, *y))
}
(SqlValue::Float(x), SqlValue::Smallint(y)) => {
Some(Self::apply_range_op(*x, op, f32::from(*y)))
}
(SqlValue::Smallint(x), SqlValue::Float(y)) => {
Some(Self::apply_range_op(f32::from(*x), op, *y))
}
(SqlValue::Double(x), SqlValue::Smallint(y)) => {
Some(Self::apply_range_op(*x, op, f64::from(*y)))
}
(SqlValue::Smallint(x), SqlValue::Double(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y))
}
(SqlValue::Real(x), SqlValue::Smallint(y)) => {
Some(Self::apply_range_op(*x, op, f64::from(*y))) }
(SqlValue::Smallint(x), SqlValue::Real(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y)) }
(SqlValue::Numeric(x), SqlValue::Smallint(y)) => {
Some(Self::apply_range_op(*x, op, f64::from(*y)))
}
(SqlValue::Smallint(x), SqlValue::Numeric(y)) => {
Some(Self::apply_range_op(f64::from(*x), op, *y))
}
(SqlValue::Integer(x), SqlValue::Varchar(s))
| (SqlValue::Integer(x), SqlValue::Character(s)) => {
s.trim().parse::<i64>().map(|y| Self::apply_range_op(*x, op, y)).ok()
}
(SqlValue::Varchar(s), SqlValue::Integer(y))
| (SqlValue::Character(s), SqlValue::Integer(y)) => {
s.trim().parse::<i64>().map(|x| Self::apply_range_op(x, op, *y)).ok()
}
(SqlValue::Bigint(x), SqlValue::Varchar(s))
| (SqlValue::Bigint(x), SqlValue::Character(s)) => {
s.trim().parse::<i64>().map(|y| Self::apply_range_op(*x, op, y)).ok()
}
(SqlValue::Varchar(s), SqlValue::Bigint(y))
| (SqlValue::Character(s), SqlValue::Bigint(y)) => {
s.trim().parse::<i64>().map(|x| Self::apply_range_op(x, op, *y)).ok()
}
(SqlValue::Float(x), SqlValue::Varchar(s))
| (SqlValue::Float(x), SqlValue::Character(s)) => {
s.trim().parse::<f64>().map(|y| Self::apply_range_op(f64::from(*x), op, y)).ok()
}
(SqlValue::Varchar(s), SqlValue::Float(y))
| (SqlValue::Character(s), SqlValue::Float(y)) => {
s.trim().parse::<f64>().map(|x| Self::apply_range_op(x, op, f64::from(*y))).ok()
}
(SqlValue::Double(x), SqlValue::Varchar(s))
| (SqlValue::Double(x), SqlValue::Character(s)) => {
s.trim().parse::<f64>().map(|y| Self::apply_range_op(*x, op, y)).ok()
}
(SqlValue::Varchar(s), SqlValue::Double(y))
| (SqlValue::Character(s), SqlValue::Double(y)) => {
s.trim().parse::<f64>().map(|x| Self::apply_range_op(x, op, *y)).ok()
}
(SqlValue::Real(x), SqlValue::Varchar(s))
| (SqlValue::Real(x), SqlValue::Character(s)) => {
s.trim().parse::<f64>().map(|y| Self::apply_range_op(*x, op, y)).ok()
}
(SqlValue::Varchar(s), SqlValue::Real(y))
| (SqlValue::Character(s), SqlValue::Real(y)) => {
s.trim().parse::<f64>().map(|x| Self::apply_range_op(x, op, *y)).ok()
}
(SqlValue::Date(x), SqlValue::Date(y)) => Some(Self::apply_range_op(x, op, y)),
(SqlValue::Time(x), SqlValue::Time(y)) => Some(Self::apply_range_op(x, op, y)),
(SqlValue::Timestamp(x), SqlValue::Timestamp(y)) => {
Some(Self::apply_range_op(x, op, y))
}
(SqlValue::Timestamp(x), SqlValue::Varchar(s))
| (SqlValue::Timestamp(x), SqlValue::Character(s)) => {
Some(Self::apply_range_op(x.to_string().as_str(), op, s.as_str()))
}
(SqlValue::Varchar(s), SqlValue::Timestamp(y))
| (SqlValue::Character(s), SqlValue::Timestamp(y)) => {
Some(Self::apply_range_op(s.as_str(), op, y.to_string().as_str()))
}
(SqlValue::Time(x), SqlValue::Varchar(s))
| (SqlValue::Time(x), SqlValue::Character(s)) => {
Some(Self::apply_range_op(x.to_string().as_str(), op, s.as_str()))
}
(SqlValue::Varchar(s), SqlValue::Time(y))
| (SqlValue::Character(s), SqlValue::Time(y)) => {
Some(Self::apply_range_op(s.as_str(), op, y.to_string().as_str()))
}
(SqlValue::Date(x), SqlValue::Varchar(s))
| (SqlValue::Date(x), SqlValue::Character(s)) => {
use std::str::FromStr;
vibesql_types::Date::from_str(s).ok().map(|d| Self::apply_range_op(*x, op, d))
}
(SqlValue::Varchar(s), SqlValue::Date(y))
| (SqlValue::Character(s), SqlValue::Date(y)) => {
use std::str::FromStr;
vibesql_types::Date::from_str(s).ok().map(|d| Self::apply_range_op(d, op, *y))
}
(SqlValue::Blob(x), SqlValue::Blob(y)) => Some(Self::apply_range_op(x, op, y)),
(SqlValue::Blob(_), SqlValue::Varchar(_) | SqlValue::Character(_)) => {
Some(matches!(op, RangeOp::GreaterThan | RangeOp::GreaterThanOrEqual))
}
(SqlValue::Varchar(_) | SqlValue::Character(_), SqlValue::Blob(_)) => {
Some(matches!(op, RangeOp::LessThan | RangeOp::LessThanOrEqual))
}
(
SqlValue::Blob(_),
SqlValue::Integer(_)
| SqlValue::Smallint(_)
| SqlValue::Bigint(_)
| SqlValue::Float(_)
| SqlValue::Real(_)
| SqlValue::Double(_)
| SqlValue::Numeric(_),
) => Some(matches!(op, RangeOp::GreaterThan | RangeOp::GreaterThanOrEqual)),
(
SqlValue::Integer(_)
| SqlValue::Smallint(_)
| SqlValue::Bigint(_)
| SqlValue::Float(_)
| SqlValue::Real(_)
| SqlValue::Double(_)
| SqlValue::Numeric(_),
SqlValue::Blob(_),
) => Some(matches!(op, RangeOp::LessThan | RangeOp::LessThanOrEqual)),
_ => None,
}
}
#[inline]
fn apply_range_op<T: PartialOrd>(left: T, op: RangeOp, right: T) -> bool {
match op {
RangeOp::LessThan => left < right,
RangeOp::LessThanOrEqual => left <= right,
RangeOp::GreaterThan => left > right,
RangeOp::GreaterThanOrEqual => left >= right,
}
}
}
#[cfg(test)]
mod tests {
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_storage::Row;
use vibesql_types::{DataType, SqlValue};
use super::*;
fn create_test_schema() -> CombinedSchema {
let columns = vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"name".to_string(),
DataType::Varchar { max_length: Some(255) },
true,
),
];
let schema = TableSchema::new("test".to_string(), columns);
CombinedSchema::from_table("test".to_string(), schema)
}
#[test]
fn test_compile_simple_equals() {
let schema = create_test_schema();
let expr = Expression::BinaryOp {
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"id", false,
))),
op: BinaryOperator::Equal,
right: Box::new(Expression::Literal(SqlValue::Integer(42))),
};
let compiled = CompiledPredicate::compile(&expr, &schema);
assert!(compiled.is_fully_compiled());
if let CompiledPredicate::Equals { col_idx, value } = compiled {
assert_eq!(col_idx, 0);
assert_eq!(value, SqlValue::Integer(42));
} else {
panic!("Expected Equals predicate");
}
}
#[test]
fn test_evaluate_equals() {
let schema = create_test_schema();
let expr = Expression::BinaryOp {
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"id", false,
))),
op: BinaryOperator::Equal,
right: Box::new(Expression::Literal(SqlValue::Integer(42))),
};
let compiled = CompiledPredicate::compile(&expr, &schema);
let row = Row::from_vec(vec![
SqlValue::Integer(42),
SqlValue::Varchar(arcstr::ArcStr::from("test")),
]);
assert_eq!(compiled.evaluate(&row), Some(true));
let row = Row::from_vec(vec![
SqlValue::Integer(99),
SqlValue::Varchar(arcstr::ArcStr::from("test")),
]);
assert_eq!(compiled.evaluate(&row), Some(false));
}
#[test]
fn test_evaluate_and() {
let schema = create_test_schema();
let expr = Expression::BinaryOp {
left: Box::new(Expression::BinaryOp {
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"id", false,
))),
op: BinaryOperator::GreaterThan,
right: Box::new(Expression::Literal(SqlValue::Integer(10))),
}),
op: BinaryOperator::And,
right: Box::new(Expression::BinaryOp {
left: Box::new(Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(
"id", false,
))),
op: BinaryOperator::LessThan,
right: Box::new(Expression::Literal(SqlValue::Integer(100))),
}),
};
let compiled = CompiledPredicate::compile(&expr, &schema);
assert!(compiled.is_fully_compiled());
let row = Row::from_vec(vec![
SqlValue::Integer(50),
SqlValue::Varchar(arcstr::ArcStr::from("test")),
]);
assert_eq!(compiled.evaluate(&row), Some(true));
let row = Row::from_vec(vec![
SqlValue::Integer(5),
SqlValue::Varchar(arcstr::ArcStr::from("test")),
]);
assert_eq!(compiled.evaluate(&row), Some(false));
let row = Row::from_vec(vec![
SqlValue::Integer(150),
SqlValue::Varchar(arcstr::ArcStr::from("test")),
]);
assert_eq!(compiled.evaluate(&row), Some(false));
}
#[test]
fn test_float_vs_integer_range_comparison() {
let result = CompiledPredicate::compare_range(
&SqlValue::Float(678.28),
RangeOp::GreaterThan,
&SqlValue::Integer(85),
);
assert_eq!(result, Some(true), "Float(678.28) > Integer(85) should be true");
let result = CompiledPredicate::compare_range(
&SqlValue::Float(50.0),
RangeOp::GreaterThan,
&SqlValue::Integer(85),
);
assert_eq!(result, Some(false), "Float(50.0) > Integer(85) should be false");
let result = CompiledPredicate::compare_range(
&SqlValue::Integer(85),
RangeOp::LessThan,
&SqlValue::Float(678.28),
);
assert_eq!(result, Some(true), "Integer(85) < Float(678.28) should be true");
let result = CompiledPredicate::compare_range(
&SqlValue::Double(678.28),
RangeOp::GreaterThan,
&SqlValue::Integer(85),
);
assert_eq!(result, Some(true), "Double(678.28) > Integer(85) should be true");
let result = CompiledPredicate::compare_range(
&SqlValue::Real(678.28),
RangeOp::GreaterThan,
&SqlValue::Integer(85),
);
assert_eq!(result, Some(true), "Real(678.28) > Integer(85) should be true");
}
#[test]
fn test_compare_range_blob_vs_blob() {
let abc = SqlValue::Blob(vec![0x61, 0x62, 0x63]);
let ab = SqlValue::Blob(vec![0x61, 0x62]);
assert_eq!(
CompiledPredicate::compare_range(&abc, RangeOp::GreaterThanOrEqual, &ab),
Some(true)
);
assert_eq!(CompiledPredicate::compare_range(&ab, RangeOp::LessThan, &abc), Some(true));
assert_eq!(CompiledPredicate::compare_range(&abc, RangeOp::LessThan, &ab), Some(false));
}
#[test]
fn test_compare_range_blob_vs_text() {
let blob = SqlValue::Blob(vec![0x61, 0x62]);
let text = SqlValue::Varchar(arcstr::ArcStr::from("abc"));
assert_eq!(
CompiledPredicate::compare_range(&blob, RangeOp::GreaterThan, &text),
Some(true)
);
assert_eq!(
CompiledPredicate::compare_range(&blob, RangeOp::GreaterThanOrEqual, &text),
Some(true)
);
assert_eq!(CompiledPredicate::compare_range(&blob, RangeOp::LessThan, &text), Some(false));
assert_eq!(CompiledPredicate::compare_range(&text, RangeOp::LessThan, &blob), Some(true));
assert_eq!(
CompiledPredicate::compare_range(&text, RangeOp::GreaterThanOrEqual, &blob),
Some(false)
);
}
#[test]
fn test_compare_range_blob_vs_numeric() {
let blob = SqlValue::Blob(vec![0x00]);
assert_eq!(
CompiledPredicate::compare_range(&blob, RangeOp::GreaterThan, &SqlValue::Integer(99)),
Some(true)
);
assert_eq!(
CompiledPredicate::compare_range(&SqlValue::Integer(99), RangeOp::LessThan, &blob),
Some(true)
);
assert_eq!(
CompiledPredicate::compare_range(&SqlValue::Real(0.5), RangeOp::LessThanOrEqual, &blob),
Some(true)
);
assert_eq!(
CompiledPredicate::compare_range(&blob, RangeOp::LessThan, &SqlValue::Real(0.5)),
Some(false)
);
}
}