use crate::core::Value;
use crate::executor::utils::{
dummy_token, extract_column_name, extract_literal_value, flip_operator, infix_to_operator,
};
use crate::parser::ast::{self as ast, InfixOperator, PrefixOperator};
use crate::parser::token::TokenType;
use crate::storage::expression::{
AndExpr, BetweenExpr, ComparisonExpr, Expression, InListExpr, LikeExpr, NotExpr, NullCheckExpr,
OrExpr,
};
pub fn convert_ast_to_storage_expr(expr: &ast::Expression) -> Option<Box<dyn Expression>> {
match expr {
ast::Expression::Infix(infix) => convert_infix(infix),
ast::Expression::Prefix(prefix) => convert_prefix(prefix),
ast::Expression::In(in_expr) => convert_in(in_expr),
ast::Expression::Between(between) => convert_between(between),
ast::Expression::Like(like) => convert_like(like),
ast::Expression::BooleanLiteral(b) => {
Some(Box::new(crate::storage::expression::ConstBoolExpr::new(
b.value,
)))
}
ast::Expression::FunctionCall(_)
| ast::Expression::ScalarSubquery(_)
| ast::Expression::Exists(_)
| ast::Expression::AllAny(_)
| ast::Expression::Case(_)
| ast::Expression::Cast(_)
| ast::Expression::Window(_)
| ast::Expression::Parameter(_)
| ast::Expression::IntervalLiteral(_) => None,
ast::Expression::Identifier(_)
| ast::Expression::QualifiedIdentifier(_)
| ast::Expression::IntegerLiteral(_)
| ast::Expression::FloatLiteral(_)
| ast::Expression::StringLiteral(_)
| ast::Expression::NullLiteral(_)
| ast::Expression::List(_)
| ast::Expression::Distinct(_)
| ast::Expression::ExpressionList(_)
| ast::Expression::Aliased(_) => None,
ast::Expression::TableSource(_)
| ast::Expression::JoinSource(_)
| ast::Expression::SubquerySource(_)
| ast::Expression::ValuesSource(_)
| ast::Expression::CteReference(_)
| ast::Expression::FunctionTableSource(_)
| ast::Expression::Star(_)
| ast::Expression::QualifiedStar(_)
| ast::Expression::Default(_)
| ast::Expression::InHashSet(_) => None,
}
}
fn convert_infix(infix: &ast::InfixExpression) -> Option<Box<dyn Expression>> {
match infix.op_type {
InfixOperator::Equal
| InfixOperator::NotEqual
| InfixOperator::LessThan
| InfixOperator::LessEqual
| InfixOperator::GreaterThan
| InfixOperator::GreaterEqual => convert_comparison(infix),
InfixOperator::And => {
let left = convert_ast_to_storage_expr(&infix.left)?;
let right = convert_ast_to_storage_expr(&infix.right)?;
Some(Box::new(AndExpr::and(left, right)))
}
InfixOperator::Or => {
let left = convert_ast_to_storage_expr(&infix.left)?;
let right = convert_ast_to_storage_expr(&infix.right)?;
Some(Box::new(OrExpr::or(left, right)))
}
InfixOperator::Is | InfixOperator::IsNot => convert_is_null(infix),
_ => None,
}
}
fn convert_comparison(infix: &ast::InfixExpression) -> Option<Box<dyn Expression>> {
if let Some((column, value)) = try_extract_column_value(&infix.left, &infix.right) {
let operator = infix_to_operator(infix.op_type)?;
return Some(Box::new(ComparisonExpr::new(column, operator, value)));
}
if let Some((column, value)) = try_extract_column_value(&infix.right, &infix.left) {
let operator = flip_operator(infix_to_operator(infix.op_type)?);
return Some(Box::new(ComparisonExpr::new(column, operator, value)));
}
None
}
fn try_extract_column_value(
maybe_column: &ast::Expression,
maybe_value: &ast::Expression,
) -> Option<(String, Value)> {
let column = extract_column_name(maybe_column)?;
let value = extract_literal_value(maybe_value)?;
Some((column, value))
}
fn convert_is_null(infix: &ast::InfixExpression) -> Option<Box<dyn Expression>> {
let column = extract_column_name(&infix.left)?;
let is_not = infix.op_type == InfixOperator::IsNot;
match &*infix.right {
ast::Expression::NullLiteral(_) => {
if is_not {
Some(Box::new(NullCheckExpr::is_not_null(column)))
} else {
Some(Box::new(NullCheckExpr::is_null(column)))
}
}
_ => None,
}
}
fn convert_prefix(prefix: &ast::PrefixExpression) -> Option<Box<dyn Expression>> {
match prefix.op_type {
PrefixOperator::Not => {
let inner = convert_ast_to_storage_expr(&prefix.right)?;
Some(Box::new(NotExpr::new(inner)))
}
_ => None,
}
}
fn convert_in(in_expr: &ast::InExpression) -> Option<Box<dyn Expression>> {
let column = extract_column_name(&in_expr.left)?;
let values: Option<Vec<Value>> = match &*in_expr.right {
ast::Expression::ExpressionList(list) => {
list.expressions.iter().map(extract_literal_value).collect()
}
ast::Expression::List(list) => list.elements.iter().map(extract_literal_value).collect(),
_ => return None, };
let values = values?;
if values.is_empty() {
return None;
}
let expr = if in_expr.not {
InListExpr::not_in(column, values)
} else {
InListExpr::new(column, values)
};
Some(Box::new(expr))
}
fn convert_between(between: &ast::BetweenExpression) -> Option<Box<dyn Expression>> {
let column = extract_column_name(&between.expr)?;
let low = extract_literal_value(&between.lower)?;
let high = extract_literal_value(&between.upper)?;
let expr = if between.not {
BetweenExpr::not_between(column, low, high)
} else {
BetweenExpr::new(column, low, high)
};
Some(Box::new(expr))
}
fn convert_like(like: &ast::LikeExpression) -> Option<Box<dyn Expression>> {
let column = extract_column_name(&like.left)?;
let pattern = match &*like.pattern {
ast::Expression::StringLiteral(s) => s.value.clone(),
_ => return None, };
let op_upper = like.operator.to_uppercase();
let is_not = op_upper.contains("NOT");
let is_ilike = op_upper.contains("ILIKE");
let expr = match (is_not, is_ilike) {
(true, true) => LikeExpr::not_ilike(column, pattern),
(true, false) => LikeExpr::not_like(column, pattern),
(false, true) => LikeExpr::new_ilike(column, pattern),
(false, false) => LikeExpr::new(column, pattern),
};
Some(Box::new(expr))
}
#[allow(dead_code)]
pub fn split_pushable_predicates(
expr: &ast::Expression,
) -> (Option<Box<dyn Expression>>, Option<ast::Expression>) {
match expr {
ast::Expression::Infix(infix) if infix.op_type == InfixOperator::And => {
let (left_push, left_remain) = split_pushable_predicates(&infix.left);
let (right_push, right_remain) = split_pushable_predicates(&infix.right);
let pushable = match (left_push, right_push) {
(Some(l), Some(r)) => Some(Box::new(AndExpr::and(l, r)) as Box<dyn Expression>),
(Some(l), None) => Some(l),
(None, Some(r)) => Some(r),
(None, None) => None,
};
let remaining = match (left_remain, right_remain) {
(Some(l), Some(r)) => Some(ast::Expression::Infix(ast::InfixExpression::new(
dummy_token("AND", TokenType::Keyword),
Box::new(l),
"AND".to_string(),
Box::new(r),
))),
(Some(l), None) => Some(l),
(None, Some(r)) => Some(r),
(None, None) => None,
};
(pushable, remaining)
}
_ => {
if let Some(storage_expr) = convert_ast_to_storage_expr(expr) {
(Some(storage_expr), None)
} else {
(None, Some(expr.clone()))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{DataType, Row, Schema, SchemaBuilder};
fn test_schema() -> Schema {
SchemaBuilder::new("test")
.add_primary_key("id", DataType::Integer)
.add("name", DataType::Text)
.add("age", DataType::Integer)
.add_nullable("email", DataType::Text)
.build()
}
fn test_row() -> Row {
Row::from_values(vec![
Value::integer(1),
Value::text("Alice"),
Value::integer(30),
Value::text("alice@example.com"),
])
}
fn make_ident(name: &str) -> ast::Expression {
ast::Expression::Identifier(ast::Identifier::new(
dummy_token(name, TokenType::Identifier),
name.to_string(),
))
}
fn make_int(value: i64) -> ast::Expression {
ast::Expression::IntegerLiteral(ast::IntegerLiteral {
token: dummy_token(&value.to_string(), TokenType::Integer),
value,
})
}
fn make_str(value: &str) -> ast::Expression {
ast::Expression::StringLiteral(ast::StringLiteral {
token: dummy_token(value, TokenType::String),
value: value.into(),
type_hint: None,
})
}
fn make_infix(left: ast::Expression, op: &str, right: ast::Expression) -> ast::InfixExpression {
ast::InfixExpression::new(
dummy_token(op, TokenType::Operator),
Box::new(left),
op,
Box::new(right),
)
}
#[test]
fn test_convert_simple_equality() {
let ast_expr = ast::Expression::Infix(make_infix(make_ident("id"), "=", make_int(1)));
let storage_expr = convert_ast_to_storage_expr(&ast_expr);
assert!(storage_expr.is_some());
let mut expr = storage_expr.unwrap();
let schema = test_schema();
expr.prepare_for_schema(&schema);
let row = test_row();
assert!(expr.evaluate(&row).unwrap());
}
#[test]
fn test_convert_and_expression() {
let left_cond = ast::Expression::Infix(make_infix(make_ident("id"), "=", make_int(1)));
let right_cond = ast::Expression::Infix(make_infix(make_ident("age"), ">", make_int(20)));
let ast_expr = ast::Expression::Infix(make_infix(left_cond, "AND", right_cond));
let storage_expr = convert_ast_to_storage_expr(&ast_expr);
assert!(storage_expr.is_some());
let mut expr = storage_expr.unwrap();
let schema = test_schema();
expr.prepare_for_schema(&schema);
let row = test_row(); assert!(expr.evaluate(&row).unwrap());
}
#[test]
fn test_convert_value_on_left() {
let ast_expr = ast::Expression::Infix(make_infix(make_int(0), "<", make_ident("id")));
let storage_expr = convert_ast_to_storage_expr(&ast_expr);
assert!(storage_expr.is_some());
let mut expr = storage_expr.unwrap();
let schema = test_schema();
expr.prepare_for_schema(&schema);
let row = test_row(); assert!(expr.evaluate(&row).unwrap()); }
#[test]
fn test_cannot_convert_function_call() {
let ast_expr = ast::Expression::FunctionCall(Box::new(ast::FunctionCall {
token: dummy_token("UPPER", TokenType::Identifier),
function: "UPPER".into(),
arguments: vec![make_ident("name")],
is_distinct: false,
order_by: vec![],
filter: None,
}));
let storage_expr = convert_ast_to_storage_expr(&ast_expr);
assert!(storage_expr.is_none());
}
#[test]
fn test_convert_string_comparison() {
let ast_expr =
ast::Expression::Infix(make_infix(make_ident("name"), "=", make_str("Alice")));
let storage_expr = convert_ast_to_storage_expr(&ast_expr);
assert!(storage_expr.is_some());
let mut expr = storage_expr.unwrap();
let schema = test_schema();
expr.prepare_for_schema(&schema);
let row = test_row();
assert!(expr.evaluate(&row).unwrap());
}
}