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