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