1use serde::{Deserialize, Serialize};
8use uqa_core::Value;
9
10use super::{FunctionBinding, SelectStmt};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Projection {
14 pub expr: Expr,
15 pub alias: Option<String>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct OrderBy {
20 pub expr: Expr,
21 pub descending: bool,
22 pub nulls: Option<NullsOrder>,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29pub enum NullsOrder {
30 First,
31 Last,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct WindowSpec {
36 pub partition_by: Vec<Expr>,
37 pub order_by: Vec<OrderBy>,
38 pub frame: Option<WindowFrame>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct WindowFrame {
45 pub mode: FrameMode,
46 pub start: FrameBound,
47 pub end: FrameBound,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum FrameMode {
52 Rows,
53 Range,
54 Groups,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub enum FrameBound {
59 UnboundedPreceding,
60 UnboundedFollowing,
61 CurrentRow,
62 Preceding(Box<Expr>),
63 Following(Box<Expr>),
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub enum Expr {
69 Star,
70 QualifiedStar(String),
72 Default,
76 Column(String),
78 QualifiedColumn {
80 qualifier: String,
81 column: String,
82 },
83 Literal(Value),
84 Param(usize),
86 Func {
89 name: String,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 binding: Option<FunctionBinding>,
92 args: Vec<Expr>,
93 distinct: bool,
96 order_by: Vec<OrderBy>,
99 filter: Option<Box<Expr>>,
101 },
102 Array(Vec<Expr>),
105 Row(Vec<Expr>),
107 Binary {
109 op: BinaryOp,
110 lhs: Box<Expr>,
111 rhs: Box<Expr>,
112 },
113 UnaryMinus(Box<Expr>),
116 Not(Box<Expr>),
118 And(Vec<Expr>),
120 Or(Vec<Expr>),
122 IsNull {
124 expr: Box<Expr>,
125 negated: bool,
126 },
127 Between {
129 expr: Box<Expr>,
130 low: Box<Expr>,
131 high: Box<Expr>,
132 },
133 InList {
135 expr: Box<Expr>,
136 list: Vec<Expr>,
137 negated: bool,
138 },
139 WindowCall {
141 name: String,
142 args: Vec<Expr>,
143 spec: WindowSpec,
144 },
145 Case {
150 base: Option<Box<Expr>>,
151 when: Vec<(Expr, Expr)>,
152 else_branch: Option<Box<Expr>>,
153 },
154 Cast {
157 expr: Box<Expr>,
158 ty: String,
159 },
160 ScalarSubquery(Box<SelectStmt>),
163 Exists {
166 body: Box<SelectStmt>,
167 negated: bool,
168 },
169 InSubquery {
173 expr: Box<Expr>,
174 body: Box<SelectStmt>,
175 negated: bool,
176 },
177}
178
179impl Expr {
180 pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
181 Self::QualifiedColumn {
182 qualifier: qualifier.into(),
183 column: column.into(),
184 }
185 }
186
187 #[must_use]
189 pub fn contains_window(&self) -> bool {
190 self.any_node(&|node| matches!(node, Self::WindowCall { .. }))
191 }
192
193 #[must_use]
195 pub fn contains_aggregate(&self) -> bool {
196 self.any_node(
197 &|node| matches!(node, Self::Func { name, .. } if is_builtin_aggregate_function(name)),
198 )
199 }
200
201 #[must_use]
203 pub fn contains_unqualified_column(&self) -> bool {
204 self.any_node(&|node| matches!(node, Self::Column(_)))
205 }
206
207 #[must_use]
209 pub fn contains_function_with_unknown_strictness(&self) -> bool {
210 self.any_node(&|node| {
211 matches!(
212 node,
213 Self::Func { name, args, .. }
214 if crate::expr::builtin_scalar_function_strictness(name, args.len()).is_none()
215 )
216 })
217 }
218
219 fn any_node(&self, hit: &dyn Fn(&Self) -> bool) -> bool {
221 if hit(self) {
222 return true;
223 }
224 match self {
225 Self::Func {
226 args,
227 order_by,
228 filter,
229 ..
230 } => {
231 args.iter().any(|arg| arg.any_node(hit))
232 || order_by.iter().any(|order| order.expr.any_node(hit))
233 || filter.as_deref().is_some_and(|filter| filter.any_node(hit))
234 }
235 Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
236 items.iter().any(|item| item.any_node(hit))
237 }
238 Self::UnaryMinus(expr) | Self::Not(expr) | Self::Cast { expr, .. } => {
239 expr.any_node(hit)
240 }
241 Self::Binary { lhs, rhs, .. } => lhs.any_node(hit) || rhs.any_node(hit),
242 Self::IsNull { expr, .. } | Self::InSubquery { expr, .. } => expr.any_node(hit),
243 Self::Between { expr, low, high } => {
244 expr.any_node(hit) || low.any_node(hit) || high.any_node(hit)
245 }
246 Self::InList { expr, list, .. } => {
247 expr.any_node(hit) || list.iter().any(|item| item.any_node(hit))
248 }
249 Self::Case {
250 base,
251 when,
252 else_branch,
253 } => {
254 base.as_deref().is_some_and(|base| base.any_node(hit))
255 || when
256 .iter()
257 .any(|(condition, result)| condition.any_node(hit) || result.any_node(hit))
258 || else_branch
259 .as_deref()
260 .is_some_and(|branch| branch.any_node(hit))
261 }
262 Self::WindowCall { .. }
263 | Self::Star
264 | Self::QualifiedStar(_)
265 | Self::Default
266 | Self::Column(_)
267 | Self::QualifiedColumn { .. }
268 | Self::Literal(_)
269 | Self::Param(_)
270 | Self::ScalarSubquery(_)
271 | Self::Exists { .. } => false,
272 }
273 }
274}
275
276#[must_use]
278pub fn is_builtin_aggregate_function(name: &str) -> bool {
279 matches!(
280 name.to_ascii_lowercase().as_str(),
281 "count"
282 | "sum"
283 | "avg"
284 | "min"
285 | "max"
286 | "string_agg"
287 | "array_agg"
288 | "bool_and"
289 | "bool_or"
290 | "stddev"
291 | "stddev_samp"
292 | "stddev_pop"
293 | "variance"
294 | "var_samp"
295 | "var_pop"
296 | "percentile_cont"
297 | "percentile_disc"
298 | "mode"
299 | "json_agg"
300 | "jsonb_agg"
301 | "json_object_agg"
302 | "jsonb_object_agg"
303 )
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
307pub enum BinaryOp {
308 Equal,
309 NotEqual,
310 Less,
311 LessEqual,
312 Greater,
313 GreaterEqual,
314 Add,
315 Subtract,
316 Multiply,
317 Divide,
318}
319
320pub type ValueExpr = Expr;