use std::borrow::Cow;
use crate::func::FunctionCall;
use crate::func::write_function_call;
use crate::query::Select;
use crate::query::write_select;
use crate::types::ColumnName;
use crate::types::ColumnRef;
use crate::types::IntoColumnRef;
use crate::types::IntoIden;
use crate::types::write_iden;
use crate::types::write_table_name;
use crate::value::Value;
use crate::writer::SqlWriter;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
#[expect(missing_docs)]
pub enum Keyword {
Null,
CurrentTimestamp,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
#[expect(missing_docs)]
pub enum Expr {
Column(ColumnRef),
Asterisk,
Keyword(Keyword),
Tuple(Vec<Expr>),
Value(Value),
Unary(UnaryOp, Box<Expr>),
Binary(Box<Expr>, BinaryOp, Box<Expr>),
FunctionCall(FunctionCall),
SubQuery(Option<SubQueryOp>, Box<Select>),
Custom(Cow<'static, str>),
}
impl Expr {
pub fn value<T>(value: T) -> Expr
where
T: Into<Value>,
{
Expr::Value(value.into())
}
pub fn column<T>(col: T) -> Self
where
T: IntoColumnRef,
{
Expr::Column(col.into_column_ref())
}
pub fn function<N, T, I>(name: N, args: I) -> Self
where
N: Into<Cow<'static, str>>,
I: IntoIterator<Item = T>,
T: Into<Expr>,
{
Expr::FunctionCall(FunctionCall::custom(name, args))
}
pub fn asterisk() -> Self {
Expr::Asterisk
}
pub fn tuple<I>(exprs: I) -> Self
where
I: IntoIterator<Item = Self>,
{
Expr::Tuple(exprs.into_iter().collect())
}
pub fn custom<T>(expr: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Expr::Custom(expr.into())
}
pub fn current_timestamp() -> Self {
Expr::Keyword(Keyword::CurrentTimestamp)
}
}
impl Expr {
pub fn max(self) -> Self {
Expr::FunctionCall(FunctionCall::max(self))
}
pub fn min(self) -> Self {
Expr::FunctionCall(FunctionCall::min(self))
}
pub fn sum(self) -> Self {
Expr::FunctionCall(FunctionCall::sum(self))
}
pub fn avg(self) -> Self {
Expr::FunctionCall(FunctionCall::avg(self))
}
pub fn count(self) -> Self {
Expr::FunctionCall(FunctionCall::count(self))
}
pub fn is_null(self) -> Self {
self.binary(BinaryOp::Is, Expr::Keyword(Keyword::Null))
}
pub fn is_not_null(self) -> Self {
self.binary(BinaryOp::IsNot, Expr::Keyword(Keyword::Null))
}
pub fn between<A, B>(self, a: A, b: B) -> Self
where
A: Into<Expr>,
B: Into<Expr>,
{
self.binary(
BinaryOp::Between,
Expr::Binary(Box::new(a.into()), BinaryOp::And, Box::new(b.into())),
)
}
pub fn not_between<A, B>(self, a: A, b: B) -> Self
where
A: Into<Expr>,
B: Into<Expr>,
{
self.binary(
BinaryOp::NotBetween,
Expr::Binary(Box::new(a.into()), BinaryOp::And, Box::new(b.into())),
)
}
pub fn like<R>(self, pattern: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::Like, pattern)
}
#[expect(clippy::should_implement_trait)]
pub fn add<R>(self, rhs: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::Add, rhs)
}
#[expect(clippy::should_implement_trait)]
pub fn sub<R>(self, rhs: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::Sub, rhs)
}
#[expect(clippy::should_implement_trait)]
pub fn mul<R>(self, rhs: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::Mul, rhs)
}
#[expect(clippy::should_implement_trait)]
pub fn div<R>(self, rhs: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::Div, rhs)
}
pub fn if_null<V>(self, value: V) -> Self
where
V: Into<Expr>,
{
Expr::FunctionCall(FunctionCall::coalesce(self, value))
}
pub fn gt<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::GreaterThan, right)
}
pub fn gte<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::GreaterThanOrEqual, right)
}
pub fn lt<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::LessThan, right)
}
pub fn lte<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::LessThanOrEqual, right)
}
pub fn contains<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::Contains, right)
}
pub fn contained_by<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::ContainedBy, right)
}
pub fn overlaps<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::Overlaps, right)
}
pub fn strictly_left_of<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::StrictlyLeftOf, right)
}
pub fn strictly_right_of<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::StrictlyRightOf, right)
}
pub fn does_not_extend_right_of<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::DoesNotExtendRightOf, right)
}
pub fn does_not_extend_left_of<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::DoesNotExtendLeftOf, right)
}
pub fn adjacent_to<R>(self, right: R) -> Self
where
R: Into<Expr>,
{
self.binary(BinaryOp::AdjacentTo, right)
}
pub fn binary<R>(self, op: BinaryOp, rhs: R) -> Self
where
R: Into<Expr>,
{
Expr::Binary(Box::new(self), op, Box::new(rhs.into()))
}
pub fn and<R>(self, right: R) -> Expr
where
R: Into<Expr>,
{
self.binary(BinaryOp::And, right)
}
pub fn or<R>(self, right: R) -> Expr
where
R: Into<Expr>,
{
self.binary(BinaryOp::Or, right)
}
pub fn eq<R>(self, right: R) -> Expr
where
R: Into<Expr>,
{
self.binary(BinaryOp::Equal, right)
}
pub fn ne<R>(self, right: R) -> Expr
where
R: Into<Expr>,
{
self.binary(BinaryOp::NotEqual, right)
}
pub fn is<R>(self, right: R) -> Expr
where
R: Into<Expr>,
{
self.binary(BinaryOp::Is, right)
}
pub fn is_not<R>(self, right: R) -> Expr
where
R: Into<Expr>,
{
self.binary(BinaryOp::IsNot, right)
}
pub fn is_in<V, I>(self, v: I) -> Expr
where
V: Into<Expr>,
I: IntoIterator<Item = V>,
{
self.binary(
BinaryOp::In,
Expr::Tuple(v.into_iter().map(|v| v.into()).collect()),
)
}
pub fn is_not_in<V, I>(self, v: I) -> Expr
where
V: Into<Expr>,
I: IntoIterator<Item = V>,
{
self.binary(
BinaryOp::NotIn,
Expr::Tuple(v.into_iter().map(|v| v.into()).collect()),
)
}
pub fn in_subquery(self, query: Select) -> Expr {
self.binary(BinaryOp::In, Expr::SubQuery(None, Box::new(query)))
}
pub fn in_tuples<T, I>(self, tuples: I) -> Expr
where
T: IntoIterator<Item = Expr>,
I: IntoIterator<Item = T>,
{
self.binary(
BinaryOp::In,
Expr::Tuple(
tuples
.into_iter()
.map(|t| Expr::Tuple(t.into_iter().collect()))
.collect(),
),
)
}
pub fn unary(self, op: UnaryOp) -> Expr {
Expr::Unary(op, Box::new(self))
}
#[expect(clippy::should_implement_trait)]
pub fn not(self) -> Expr {
self.unary(UnaryOp::Not)
}
pub fn cast_as<N>(self, ty: N) -> Expr
where
N: IntoIden,
{
Expr::FunctionCall(FunctionCall::cast_as(self, ty))
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[non_exhaustive]
#[expect(missing_docs)]
pub enum SubQueryOp {
Exists,
Any,
Some,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
#[expect(missing_docs)]
pub enum UnaryOp {
Not,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
#[expect(missing_docs)]
pub enum BinaryOp {
And,
Or,
As,
Equal,
NotEqual,
Between,
NotBetween,
Like,
NotLike,
Is,
IsNot,
In,
NotIn,
LShift,
RShift,
Add,
Sub,
Mul,
Div,
Mod,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Contains,
ContainedBy,
Overlaps,
StrictlyLeftOf,
StrictlyRightOf,
DoesNotExtendRightOf,
DoesNotExtendLeftOf,
AdjacentTo,
}
impl Expr {
pub(crate) fn from_conditions(conditions: Vec<Expr>) -> Option<Expr> {
conditions
.into_iter()
.reduce(|lhs, rhs| lhs.binary(BinaryOp::And, rhs))
}
}
impl From<Keyword> for Expr {
fn from(k: Keyword) -> Self {
Expr::Keyword(k)
}
}
impl<T> From<T> for Expr
where
T: Into<Value>,
{
fn from(v: T) -> Self {
Expr::Value(v.into())
}
}
pub(crate) fn write_expr<W: SqlWriter>(w: &mut W, expr: &Expr) {
match expr {
Expr::Column(col) => write_column_ref(w, col),
Expr::Asterisk => w.push_char('*'),
Expr::Keyword(keyword) => w.push_str(match keyword {
Keyword::Null => "NULL",
Keyword::CurrentTimestamp => "CURRENT_TIMESTAMP",
}),
Expr::Tuple(exprs) => write_tuple(w, exprs),
Expr::Value(value) => w.push_param(value.clone()),
Expr::Unary(unary, expr) => write_unary_expr(w, unary, expr),
Expr::Binary(lhs, op, rhs) => match (op, &**rhs) {
(BinaryOp::In, Expr::Tuple(t)) if t.is_empty() => {
write_binary_expr(w, &Expr::value(1), &BinaryOp::Equal, &Expr::value(2))
}
(BinaryOp::NotIn, Expr::Tuple(t)) if t.is_empty() => {
write_binary_expr(w, &Expr::value(1), &BinaryOp::Equal, &Expr::value(1))
}
_ => write_binary_expr(w, lhs, op, rhs),
},
Expr::FunctionCall(call) => write_function_call(w, call),
Expr::SubQuery(op, query) => {
if let Some(op) = op {
w.push_str(match op {
SubQueryOp::Exists => "EXISTS",
SubQueryOp::Any => "ANY",
SubQueryOp::Some => "SOME",
SubQueryOp::All => "ALL",
});
}
w.push_char('(');
write_select(w, query);
w.push_char(')');
}
Expr::Custom(expr) => w.push_str(expr),
}
}
fn write_unary_expr<W: SqlWriter>(w: &mut W, op: &UnaryOp, expr: &Expr) {
write_unary_op(w, op);
w.push_char(' ');
let mut paren = true;
paren &= !well_known_no_parentheses(expr);
paren &= !well_known_high_precedence(expr, &Operator::Unary(*op));
if paren {
w.push_char('(');
}
write_expr(w, expr);
if paren {
w.push_char(')');
}
}
fn write_unary_op<W: SqlWriter>(w: &mut W, op: &UnaryOp) {
w.push_str(match op {
UnaryOp::Not => "NOT",
})
}
fn write_binary_expr<W: SqlWriter>(w: &mut W, lhs: &Expr, op: &BinaryOp, rhs: &Expr) {
let binop = Operator::Binary(*op);
let mut left_paren = true;
left_paren &= !well_known_no_parentheses(lhs);
left_paren &= !well_known_high_precedence(lhs, &binop);
if left_paren
&& let Expr::Binary(_, inner_op, _) = lhs
&& inner_op == op
&& well_known_left_associative(op)
{
left_paren = false;
}
if left_paren {
w.push_char('(');
}
write_expr(w, lhs);
if left_paren {
w.push_char(')');
}
w.push_char(' ');
write_binary_op(w, op);
w.push_char(' ');
let mut right_paren = true;
right_paren &= !well_known_no_parentheses(rhs);
right_paren &= !well_known_high_precedence(rhs, &binop);
if right_paren
&& binop.is_between()
&& let Expr::Binary(_, BinaryOp::And, _) = rhs
{
right_paren = false;
}
if right_paren && (op == &BinaryOp::As) && matches!(rhs, Expr::Custom(_)) {
right_paren = false;
}
if right_paren {
w.push_char('(');
}
write_expr(w, rhs);
if right_paren {
w.push_char(')');
}
}
fn write_binary_op<W: SqlWriter>(w: &mut W, op: &BinaryOp) {
w.push_str(match op {
BinaryOp::And => "AND",
BinaryOp::Or => "OR",
BinaryOp::As => "AS",
BinaryOp::Like => "LIKE",
BinaryOp::NotLike => "NOT LIKE",
BinaryOp::Is => "IS",
BinaryOp::IsNot => "IS NOT",
BinaryOp::In => "IN",
BinaryOp::NotIn => "NOT IN",
BinaryOp::Between => "BETWEEN",
BinaryOp::NotBetween => "NOT BETWEEN",
BinaryOp::Equal => "=",
BinaryOp::NotEqual => "<>",
BinaryOp::LessThan => "<",
BinaryOp::LessThanOrEqual => "<=",
BinaryOp::GreaterThan => ">",
BinaryOp::GreaterThanOrEqual => ">=",
BinaryOp::Contains => "@>",
BinaryOp::ContainedBy => "<@",
BinaryOp::Overlaps => "&&",
BinaryOp::StrictlyLeftOf => "<<",
BinaryOp::StrictlyRightOf => ">>",
BinaryOp::DoesNotExtendRightOf => "&<",
BinaryOp::DoesNotExtendLeftOf => "&>",
BinaryOp::AdjacentTo => "-|-",
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
BinaryOp::Mod => "%",
BinaryOp::LShift => "<<",
BinaryOp::RShift => ">>",
})
}
pub(crate) fn write_tuple<W: SqlWriter>(w: &mut W, exprs: &[Expr]) {
w.push_char('(');
for (i, expr) in exprs.iter().enumerate() {
if i != 0 {
w.push_str(", ");
}
write_expr(w, expr);
}
w.push_char(')');
}
fn write_column_ref<W: SqlWriter>(w: &mut W, col: &ColumnRef) {
match col {
ColumnRef::Column(ColumnName(table_name, column)) => {
if let Some(table_name) = table_name {
write_table_name(w, table_name);
w.push_char('.');
}
write_iden(w, column);
}
ColumnRef::Asterisk(table_name) => {
if let Some(table_name) = table_name {
write_table_name(w, table_name);
w.push_char('.');
}
w.push_char('*');
}
}
}
fn well_known_no_parentheses(expr: &Expr) -> bool {
matches!(
expr,
Expr::Column(_)
| Expr::Tuple(_)
| Expr::Value(_)
| Expr::Asterisk
| Expr::Keyword(_)
| Expr::FunctionCall(_)
| Expr::SubQuery(_, _)
)
}
fn well_known_left_associative(op: &BinaryOp) -> bool {
matches!(
op,
BinaryOp::And
| BinaryOp::Or
| BinaryOp::Add
| BinaryOp::Sub
| BinaryOp::Mul
| BinaryOp::Div
)
}
fn well_known_high_precedence(expr: &Expr, outer_op: &Operator) -> bool {
let inner_op = if let Expr::Binary(_, op, _) = expr {
Operator::Binary(*op)
} else {
return false;
};
if inner_op.is_arithmetic() || inner_op.is_shift() {
return outer_op.is_comparison()
|| outer_op.is_between()
|| outer_op.is_in()
|| outer_op.is_like()
|| outer_op.is_logical();
}
if inner_op.is_comparison() || inner_op.is_in() || inner_op.is_like() || inner_op.is_is() {
return outer_op.is_logical();
}
false
}
enum Operator {
Unary(UnaryOp),
Binary(BinaryOp),
}
impl Operator {
fn is_logical(&self) -> bool {
matches!(
self,
Operator::Unary(UnaryOp::Not)
| Operator::Binary(BinaryOp::And)
| Operator::Binary(BinaryOp::Or)
)
}
fn is_between(&self) -> bool {
matches!(
self,
Operator::Binary(BinaryOp::Between) | Operator::Binary(BinaryOp::NotBetween)
)
}
fn is_like(&self) -> bool {
matches!(
self,
Operator::Binary(BinaryOp::Like) | Operator::Binary(BinaryOp::NotLike)
)
}
fn is_in(&self) -> bool {
matches!(
self,
Operator::Binary(BinaryOp::In) | Operator::Binary(BinaryOp::NotIn)
)
}
fn is_is(&self) -> bool {
matches!(
self,
Operator::Binary(BinaryOp::Is) | Operator::Binary(BinaryOp::IsNot)
)
}
fn is_shift(&self) -> bool {
matches!(
self,
Operator::Binary(BinaryOp::LShift) | Operator::Binary(BinaryOp::RShift)
)
}
fn is_arithmetic(&self) -> bool {
match self {
Operator::Binary(b) => {
matches!(
b,
BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod | BinaryOp::Add | BinaryOp::Sub
)
}
_ => false,
}
}
fn is_comparison(&self) -> bool {
match self {
Operator::Binary(b) => {
matches!(
b,
BinaryOp::LessThan
| BinaryOp::LessThanOrEqual
| BinaryOp::Equal
| BinaryOp::GreaterThanOrEqual
| BinaryOp::GreaterThan
| BinaryOp::NotEqual
| BinaryOp::Contains
| BinaryOp::ContainedBy
| BinaryOp::Overlaps
| BinaryOp::StrictlyLeftOf
| BinaryOp::StrictlyRightOf
| BinaryOp::DoesNotExtendRightOf
| BinaryOp::DoesNotExtendLeftOf
| BinaryOp::AdjacentTo
)
}
_ => false,
}
}
}