use serde::{Deserialize, Serialize};
use uqa_core::Value;
use super::{FunctionBinding, SelectStmt};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Projection {
pub expr: Expr,
pub alias: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBy {
pub expr: Expr,
pub descending: bool,
pub nulls: Option<NullsOrder>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NullsOrder {
First,
Last,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowSpec {
pub partition_by: Vec<Expr>,
pub order_by: Vec<OrderBy>,
pub frame: Option<WindowFrame>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowFrame {
pub mode: FrameMode,
pub start: FrameBound,
pub end: FrameBound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FrameMode {
Rows,
Range,
Groups,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FrameBound {
UnboundedPreceding,
UnboundedFollowing,
CurrentRow,
Preceding(Box<Expr>),
Following(Box<Expr>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Expr {
Star,
QualifiedStar(String),
Default,
Column(String),
QualifiedColumn {
qualifier: String,
column: String,
},
Literal(Value),
Param(usize),
Func {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
binding: Option<FunctionBinding>,
args: Vec<Expr>,
distinct: bool,
order_by: Vec<OrderBy>,
filter: Option<Box<Expr>>,
},
Array(Vec<Expr>),
Row(Vec<Expr>),
Binary {
op: BinaryOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
UnaryMinus(Box<Expr>),
Not(Box<Expr>),
And(Vec<Expr>),
Or(Vec<Expr>),
IsNull {
expr: Box<Expr>,
negated: bool,
},
Between {
expr: Box<Expr>,
low: Box<Expr>,
high: Box<Expr>,
},
InList {
expr: Box<Expr>,
list: Vec<Expr>,
negated: bool,
},
WindowCall {
name: String,
args: Vec<Expr>,
spec: WindowSpec,
},
Case {
base: Option<Box<Expr>>,
when: Vec<(Expr, Expr)>,
else_branch: Option<Box<Expr>>,
},
Cast {
expr: Box<Expr>,
ty: String,
},
ScalarSubquery(Box<SelectStmt>),
Exists {
body: Box<SelectStmt>,
negated: bool,
},
InSubquery {
expr: Box<Expr>,
body: Box<SelectStmt>,
negated: bool,
},
}
impl Expr {
pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
Self::QualifiedColumn {
qualifier: qualifier.into(),
column: column.into(),
}
}
#[must_use]
pub fn contains_window(&self) -> bool {
self.any_node(&|node| matches!(node, Self::WindowCall { .. }))
}
#[must_use]
pub fn contains_aggregate(&self) -> bool {
self.any_node(
&|node| matches!(node, Self::Func { name, .. } if is_builtin_aggregate_function(name)),
)
}
#[must_use]
pub fn contains_unqualified_column(&self) -> bool {
self.any_node(&|node| matches!(node, Self::Column(_)))
}
#[must_use]
pub fn contains_function_with_unknown_strictness(&self) -> bool {
self.any_node(&|node| {
matches!(
node,
Self::Func { name, args, .. }
if crate::expr::builtin_scalar_function_strictness(name, args.len()).is_none()
)
})
}
fn any_node(&self, hit: &dyn Fn(&Self) -> bool) -> bool {
if hit(self) {
return true;
}
match self {
Self::Func {
args,
order_by,
filter,
..
} => {
args.iter().any(|arg| arg.any_node(hit))
|| order_by.iter().any(|order| order.expr.any_node(hit))
|| filter.as_deref().is_some_and(|filter| filter.any_node(hit))
}
Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
items.iter().any(|item| item.any_node(hit))
}
Self::UnaryMinus(expr) | Self::Not(expr) | Self::Cast { expr, .. } => {
expr.any_node(hit)
}
Self::Binary { lhs, rhs, .. } => lhs.any_node(hit) || rhs.any_node(hit),
Self::IsNull { expr, .. } | Self::InSubquery { expr, .. } => expr.any_node(hit),
Self::Between { expr, low, high } => {
expr.any_node(hit) || low.any_node(hit) || high.any_node(hit)
}
Self::InList { expr, list, .. } => {
expr.any_node(hit) || list.iter().any(|item| item.any_node(hit))
}
Self::Case {
base,
when,
else_branch,
} => {
base.as_deref().is_some_and(|base| base.any_node(hit))
|| when
.iter()
.any(|(condition, result)| condition.any_node(hit) || result.any_node(hit))
|| else_branch
.as_deref()
.is_some_and(|branch| branch.any_node(hit))
}
Self::WindowCall { .. }
| Self::Star
| Self::QualifiedStar(_)
| Self::Default
| Self::Column(_)
| Self::QualifiedColumn { .. }
| Self::Literal(_)
| Self::Param(_)
| Self::ScalarSubquery(_)
| Self::Exists { .. } => false,
}
}
}
#[must_use]
pub fn is_builtin_aggregate_function(name: &str) -> bool {
matches!(
name.to_ascii_lowercase().as_str(),
"count"
| "sum"
| "avg"
| "min"
| "max"
| "string_agg"
| "array_agg"
| "bool_and"
| "bool_or"
| "stddev"
| "stddev_samp"
| "stddev_pop"
| "variance"
| "var_samp"
| "var_pop"
| "percentile_cont"
| "percentile_disc"
| "mode"
| "json_agg"
| "jsonb_agg"
| "json_object_agg"
| "jsonb_object_agg"
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOp {
Equal,
NotEqual,
Less,
LessEqual,
Greater,
GreaterEqual,
Add,
Subtract,
Multiply,
Divide,
}
pub type ValueExpr = Expr;