1mod call_arguments;
10mod traversal;
11pub use call_arguments::{
12 analyze_expression_call_arguments, scalar_call_argument, scalar_call_arguments,
13 scalar_call_arguments_with_control, validate_scalar_call_arguments, ScalarCallArgument,
14};
15
16use crate::ast::{
17 BinaryOp, ColumnType, FrameExclusion, FrameMode, FunctionBinding, FunctionOrderSyntax,
18 InternalColumnRef, NullsOrder, WindowCallModifiers,
19};
20use uqa_core::Value;
21
22pub type SubqueryId = usize;
24
25#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
26pub enum ScalarExpr {
27 Star,
28 QualifiedStar(String),
29 Default,
30 Column(String),
31 Position(usize),
33 InternalColumn(InternalColumnRef),
35 QualifiedColumn {
36 qualifier: String,
37 column: String,
38 },
39 Literal(Value),
40 TypedLiteral {
42 value: Value,
43 ty: String,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 bound_type: Option<ColumnType>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 parameter_index: Option<usize>,
50 },
51 Param(usize),
52 Func {
53 name: String,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 binding: Option<FunctionBinding>,
56 args: Vec<Self>,
57 distinct: bool,
58 order_by: Vec<ScalarOrder>,
59 #[serde(default, skip_serializing_if = "FunctionOrderSyntax::is_legacy")]
60 order_syntax: FunctionOrderSyntax,
61 filter: Option<Box<Self>>,
62 },
63 Array(Vec<Self>),
64 Row(Vec<Self>),
65 CompositeRow {
66 items: Vec<Self>,
67 binding: crate::ast::CompositeRowBinding,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
70 bound_type: Option<ColumnType>,
71 },
72 Binary {
73 op: BinaryOp,
74 lhs: Box<Self>,
75 rhs: Box<Self>,
76 },
77 UnaryMinus(Box<Self>),
78 Not(Box<Self>),
79 And(Vec<Self>),
80 Or(Vec<Self>),
81 IsNull {
82 expr: Box<Self>,
83 negated: bool,
84 },
85 Between {
86 expr: Box<Self>,
87 low: Box<Self>,
88 high: Box<Self>,
89 },
90 InList {
91 expr: Box<Self>,
92 list: Vec<Self>,
93 negated: bool,
94 },
95 WindowCall {
97 name: String,
98 args: Vec<Self>,
99 spec: ScalarWindowSpec,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 filter: Option<Box<Self>>,
102 #[serde(default, skip_serializing_if = "WindowCallModifiers::is_empty")]
103 modifiers: WindowCallModifiers,
104 },
105 Case {
106 base: Option<Box<Self>>,
107 when: Vec<(Self, Self)>,
108 else_branch: Option<Box<Self>>,
109 },
110 Cast {
111 #[serde(default, skip_serializing_if = "is_false")]
113 implicit: bool,
114 expr: Box<Self>,
115 ty: String,
116 },
117 ScalarSubquery(SubqueryId),
118 Exists {
119 subquery: SubqueryId,
120 negated: bool,
121 },
122 InSubquery {
123 expr: Box<Self>,
124 subquery: SubqueryId,
125 negated: bool,
126 },
127}
128
129#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
130pub struct ScalarOrder {
131 pub expr: ScalarExpr,
132 pub descending: bool,
133 pub nulls: Option<NullsOrder>,
134}
135
136#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
137pub struct ScalarWindowSpec {
138 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub definition: Option<usize>,
141 pub partition_by: Vec<ScalarExpr>,
142 pub order_by: Vec<ScalarOrder>,
143 pub frame: Option<ScalarWindowFrame>,
144}
145
146impl ScalarWindowSpec {
147 pub fn expressions(&self) -> impl Iterator<Item = &ScalarExpr> {
149 self.partition_by
150 .iter()
151 .chain(self.order_by.iter().map(|order| &order.expr))
152 .chain(
153 self.frame
154 .iter()
155 .flat_map(|frame| [&frame.start, &frame.end])
156 .filter_map(|bound| match bound {
157 ScalarFrameBound::Preceding(value) | ScalarFrameBound::Following(value) => {
158 Some(value.as_ref())
159 }
160 _ => None,
161 }),
162 )
163 }
164
165 pub fn expressions_mut(&mut self) -> impl Iterator<Item = &mut ScalarExpr> {
167 self.partition_by
168 .iter_mut()
169 .chain(self.order_by.iter_mut().map(|order| &mut order.expr))
170 .chain(
171 self.frame
172 .iter_mut()
173 .flat_map(|frame| [&mut frame.start, &mut frame.end])
174 .filter_map(|bound| match bound {
175 ScalarFrameBound::Preceding(value) | ScalarFrameBound::Following(value) => {
176 Some(value.as_mut())
177 }
178 _ => None,
179 }),
180 )
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
185pub struct ScalarWindowFrame {
186 pub mode: FrameMode,
187 pub start: ScalarFrameBound,
188 pub end: ScalarFrameBound,
189 #[serde(default = "frame_written_between")]
191 pub between: bool,
192 #[serde(default, skip_serializing_if = "FrameExclusion::is_no_others")]
193 pub exclusion: FrameExclusion,
194}
195
196const fn frame_written_between() -> bool {
198 true
199}
200
201#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
202pub enum ScalarFrameBound {
203 UnboundedPreceding,
204 UnboundedFollowing,
205 CurrentRow,
206 Preceding(Box<ScalarExpr>),
207 Following(Box<ScalarExpr>),
208}
209
210impl ScalarExpr {
211 #[must_use]
212 pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
213 Self::QualifiedColumn {
214 qualifier: qualifier.into(),
215 column: column.into(),
216 }
217 }
218}
219
220#[expect(
221 clippy::trivially_copy_pass_by_ref,
222 reason = "serde skip_serializing_if requires a borrowed field"
223)]
224const fn is_false(value: &bool) -> bool {
225 !*value
226}
227
228#[cfg(test)]
229mod tests {
230 use super::ScalarExpr;
231
232 #[test]
233 fn syntax_cast_origin_survives_owned_and_borrowed_lowering() {
234 use crate::{ast::Expr, plan::ExpressionPlan};
235 use uqa_core::{memory::MemoryBudget, CancellationToken};
236
237 let legacy = r#"{"Cast":{"expr":{"Column":"value"},"ty":"bigint[]"}}"#;
238 let explicit: Expr = serde_json::from_str(legacy).unwrap();
239 assert!(matches!(
240 explicit,
241 Expr::Cast {
242 implicit: false,
243 ..
244 }
245 ));
246 assert_eq!(serde_json::to_string(&explicit).unwrap(), legacy);
247 let mut implicit = explicit;
248 let Expr::Cast {
249 implicit: origin, ..
250 } = &mut implicit
251 else {
252 unreachable!()
253 };
254 *origin = true;
255 let stored = serde_json::to_string(&implicit).unwrap();
256 let restored: Expr = serde_json::from_str(&stored).unwrap();
257 let expected = ExpressionPlan::lower(restored.clone()).scalar;
258 assert!(matches!(expected, ScalarExpr::Cast { implicit: true, .. }));
259 let budget = MemoryBudget::new(1 << 20);
260 let token = CancellationToken::new();
261 let borrowed =
262 ExpressionPlan::lower_column_budgeted(&restored, &budget, &token, &token).unwrap();
263 assert_eq!(*borrowed, expected);
264 }
265
266 #[test]
267 fn cast_origin_survives_storage_and_legacy_casts_remain_explicit() {
268 let legacy = r#"{"Cast":{"expr":{"Column":"value"},"ty":"bigint[]"}}"#;
269 let explicit: ScalarExpr = serde_json::from_str(legacy).unwrap();
270 assert!(matches!(
271 explicit,
272 ScalarExpr::Cast {
273 implicit: false,
274 ..
275 }
276 ));
277 assert_eq!(serde_json::to_string(&explicit).unwrap(), legacy);
278 let mut implicit = explicit.clone();
279 let ScalarExpr::Cast {
280 implicit: origin, ..
281 } = &mut implicit
282 else {
283 unreachable!()
284 };
285 *origin = true;
286 let stored = serde_json::to_string(&implicit).unwrap();
287 assert_eq!(
288 serde_json::from_str::<ScalarExpr>(&stored).unwrap(),
289 implicit
290 );
291 assert_ne!(implicit, explicit);
292 }
293}