use core::fmt;
use crate::lexer::Span;
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Integer(i64),
Float(f64),
String(String),
Blob(Vec<u8>),
Boolean(bool),
Null,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Mod,
Eq,
NotEq,
Lt,
LtEq,
Gt,
GtEq,
And,
Or,
Concat,
Like,
BitAnd,
BitOr,
LeftShift,
RightShift,
}
impl BinaryOp {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::Mod => "%",
Self::Eq => "=",
Self::NotEq => "!=",
Self::Lt => "<",
Self::LtEq => "<=",
Self::Gt => ">",
Self::GtEq => ">=",
Self::And => "AND",
Self::Or => "OR",
Self::Concat => "||",
Self::Like => "LIKE",
Self::BitAnd => "&",
Self::BitOr => "|",
Self::LeftShift => "<<",
Self::RightShift => ">>",
}
}
#[must_use]
pub const fn precedence(&self) -> u8 {
match self {
Self::Or => 1,
Self::And => 2,
Self::Eq | Self::NotEq | Self::Lt | Self::LtEq | Self::Gt | Self::GtEq => 3,
Self::Like => 4,
Self::BitOr => 5,
Self::BitAnd => 6,
Self::LeftShift | Self::RightShift => 7,
Self::Add | Self::Sub | Self::Concat => 8,
Self::Mul | Self::Div | Self::Mod => 9,
}
}
}
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Integer(n) => write!(f, "{n}"),
Self::Float(v) => write!(f, "{v}"),
Self::String(s) => {
let escaped = s.replace('\'', "''");
write!(f, "'{escaped}'")
}
Self::Blob(bytes) => {
write!(f, "X'")?;
for b in bytes {
write!(f, "{b:02X}")?;
}
write!(f, "'")
}
Self::Boolean(true) => write!(f, "TRUE"),
Self::Boolean(false) => write!(f, "FALSE"),
Self::Null => write!(f, "NULL"),
}
}
}
impl fmt::Display for BinaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Neg,
Not,
BitNot,
}
impl UnaryOp {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Neg => "-",
Self::Not => "NOT",
Self::BitNot => "~",
}
}
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionCall {
pub name: String,
pub args: Vec<Expr>,
pub distinct: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Literal(Literal),
Column {
table: Option<String>,
name: String,
span: Span,
},
Binary {
left: Box<Expr>,
op: BinaryOp,
right: Box<Expr>,
},
Unary {
op: UnaryOp,
operand: Box<Expr>,
},
Function(FunctionCall),
Subquery(Box<super::SelectStatement>),
IsNull {
expr: Box<Expr>,
negated: bool,
},
In {
expr: Box<Expr>,
list: Vec<Expr>,
negated: bool,
},
Between {
expr: Box<Expr>,
low: Box<Expr>,
high: Box<Expr>,
negated: bool,
},
Case {
operand: Option<Box<Expr>>,
when_clauses: Vec<(Expr, Expr)>,
else_clause: Option<Box<Expr>>,
},
Cast {
expr: Box<Expr>,
data_type: super::DataType,
},
Paren(Box<Expr>),
Parameter {
name: Option<String>,
position: usize,
},
Wildcard {
table: Option<String>,
},
}
impl fmt::Display for FunctionCall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.name == "EXISTS" {
if let [Expr::Subquery(q)] = self.args.as_slice() {
return write!(f, "EXISTS({q})");
}
}
write!(f, "{}(", self.name)?;
if self.distinct {
write!(f, "DISTINCT ")?;
}
for (i, arg) in self.args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{arg}")?;
}
write!(f, ")")
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Literal(lit) => write!(f, "{lit}"),
Self::Column { table, name, .. } => {
if let Some(t) = table {
write!(f, "{t}.{name}")
} else {
write!(f, "{name}")
}
}
Self::Binary { left, op, right } => {
write!(f, "{left} {op} {right}")
}
Self::Unary { op, operand } => match op {
UnaryOp::Not => write!(f, "NOT {operand}"),
UnaryOp::Neg => write!(f, "-{operand}"),
UnaryOp::BitNot => write!(f, "~{operand}"),
},
Self::Function(func) => write!(f, "{func}"),
Self::Subquery(q) => write!(f, "({q})"),
Self::IsNull { expr, negated } => {
if *negated {
write!(f, "{expr} IS NOT NULL")
} else {
write!(f, "{expr} IS NULL")
}
}
Self::In {
expr,
list,
negated,
} => {
write!(f, "{expr}")?;
if *negated {
write!(f, " NOT IN (")?;
} else {
write!(f, " IN (")?;
}
for (i, item) in list.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
write!(f, ")")
}
Self::Between {
expr,
low,
high,
negated,
} => {
if *negated {
write!(f, "{expr} NOT BETWEEN {low} AND {high}")
} else {
write!(f, "{expr} BETWEEN {low} AND {high}")
}
}
Self::Case {
operand,
when_clauses,
else_clause,
} => {
write!(f, "CASE")?;
if let Some(op) = operand {
write!(f, " {op}")?;
}
for (when, then) in when_clauses {
write!(f, " WHEN {when} THEN {then}")?;
}
if let Some(el) = else_clause {
write!(f, " ELSE {el}")?;
}
write!(f, " END")
}
Self::Cast { expr, data_type } => {
write!(f, "CAST({expr} AS {data_type})")
}
Self::Paren(inner) => write!(f, "({inner})"),
Self::Parameter { name, .. } => {
if let Some(n) = name {
write!(f, ":{n}")
} else {
write!(f, "?")
}
}
Self::Wildcard { table } => {
if let Some(t) = table {
write!(f, "{t}.*")
} else {
write!(f, "*")
}
}
}
}
}
impl Expr {
#[must_use]
pub fn column(name: impl Into<String>) -> Self {
Self::Column {
table: None,
name: name.into(),
span: Span::default(),
}
}
#[must_use]
pub fn qualified_column(table: impl Into<String>, name: impl Into<String>) -> Self {
Self::Column {
table: Some(table.into()),
name: name.into(),
span: Span::default(),
}
}
#[must_use]
pub const fn integer(value: i64) -> Self {
Self::Literal(Literal::Integer(value))
}
#[must_use]
pub const fn float(value: f64) -> Self {
Self::Literal(Literal::Float(value))
}
#[must_use]
pub fn string(value: impl Into<String>) -> Self {
Self::Literal(Literal::String(value.into()))
}
#[must_use]
pub const fn boolean(value: bool) -> Self {
Self::Literal(Literal::Boolean(value))
}
#[must_use]
pub const fn null() -> Self {
Self::Literal(Literal::Null)
}
#[must_use]
pub fn binary(self, op: BinaryOp, right: Self) -> Self {
Self::Binary {
left: Box::new(self),
op,
right: Box::new(right),
}
}
#[must_use]
pub fn eq(self, right: Self) -> Self {
self.binary(BinaryOp::Eq, right)
}
#[must_use]
pub fn not_eq(self, right: Self) -> Self {
self.binary(BinaryOp::NotEq, right)
}
#[must_use]
pub fn lt(self, right: Self) -> Self {
self.binary(BinaryOp::Lt, right)
}
#[must_use]
pub fn lt_eq(self, right: Self) -> Self {
self.binary(BinaryOp::LtEq, right)
}
#[must_use]
pub fn gt(self, right: Self) -> Self {
self.binary(BinaryOp::Gt, right)
}
#[must_use]
pub fn gt_eq(self, right: Self) -> Self {
self.binary(BinaryOp::GtEq, right)
}
#[must_use]
pub fn and(self, right: Self) -> Self {
self.binary(BinaryOp::And, right)
}
#[must_use]
pub fn or(self, right: Self) -> Self {
self.binary(BinaryOp::Or, right)
}
#[must_use]
pub fn is_null(self) -> Self {
Self::IsNull {
expr: Box::new(self),
negated: false,
}
}
#[must_use]
pub fn is_not_null(self) -> Self {
Self::IsNull {
expr: Box::new(self),
negated: true,
}
}
#[must_use]
pub fn between(self, low: Self, high: Self) -> Self {
Self::Between {
expr: Box::new(self),
low: Box::new(low),
high: Box::new(high),
negated: false,
}
}
#[must_use]
pub fn not_between(self, low: Self, high: Self) -> Self {
Self::Between {
expr: Box::new(self),
low: Box::new(low),
high: Box::new(high),
negated: true,
}
}
#[must_use]
pub fn in_list(self, list: Vec<Self>) -> Self {
Self::In {
expr: Box::new(self),
list,
negated: false,
}
}
#[must_use]
pub fn not_in_list(self, list: Vec<Self>) -> Self {
Self::In {
expr: Box::new(self),
list,
negated: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binary_op_precedence() {
assert!(BinaryOp::Mul.precedence() > BinaryOp::Add.precedence());
assert!(BinaryOp::And.precedence() > BinaryOp::Or.precedence());
assert!(BinaryOp::Eq.precedence() > BinaryOp::And.precedence());
}
#[test]
fn test_expr_builders() {
let col = Expr::column("name");
assert!(matches!(col, Expr::Column { name, .. } if name == "name"));
let lit = Expr::integer(42);
assert!(matches!(lit, Expr::Literal(Literal::Integer(42))));
}
#[test]
fn test_expr_chaining() {
let expr = Expr::column("age")
.gt(Expr::integer(18))
.and(Expr::column("status").eq(Expr::string("active")));
assert!(matches!(
expr,
Expr::Binary {
op: BinaryOp::And,
..
}
));
}
}