1use super::{
10 AggregateClassifier, Expr, FrameBound, OrderBy, QueryPlan, ScalarExpr, ScalarFrameBound,
11 ScalarOrder, ScalarWindowFrame, ScalarWindowSpec, WindowSpec,
12};
13use crate::schema::retention::CatalogRetentionError;
14use resources::{Control, Lowering, Result};
15use source::{Node, Source};
16use uqa_core::{
17 memory::{Budgeted, MemoryBudget},
18 CancellationToken,
19};
20
21mod binding;
22mod copy;
23mod resources;
24mod source;
25mod window;
26
27impl super::ExpressionPlan {
28 pub fn lower_column_budgeted(
30 expression: &Expr,
31 budget: &MemoryBudget,
32 original: &CancellationToken,
33 invoking: &CancellationToken,
34 ) -> Result<Budgeted<ScalarExpr>> {
35 let mut lowering = Lowering {
36 control: Some(Control::new(budget, original, invoking)),
37 };
38 let scalar = lowering.expression(
39 Source::Borrowed(expression),
40 &super::NoRegisteredAggregates,
41 &mut Vec::new(),
42 )?;
43 lowering.finish(scalar)
44 }
45}
46
47pub(super) fn lower_scalar_expression(
48 expression: Expr,
49 aggregates: &dyn AggregateClassifier,
50 subqueries: &mut Vec<QueryPlan>,
51) -> ScalarExpr {
52 Lowering { control: None }
53 .expression(Source::Owned(expression), aggregates, subqueries)
54 .expect("owned lowering has no admission failure")
55}
56
57pub(super) fn lower_window_spec(
58 specification: WindowSpec,
59 aggregates: &dyn AggregateClassifier,
60 subqueries: &mut Vec<QueryPlan>,
61) -> ScalarWindowSpec {
62 Lowering { control: None }
63 .window(Source::Owned(specification), aggregates, subqueries)
64 .expect("owned lowering has no admission failure")
65}
66
67impl Lowering<'_> {
68 #[expect(
69 clippy::too_many_lines,
70 reason = "plan lowering preserves exhaustive variants and structural identities"
71 )]
72 fn expression(
73 &mut self,
74 expression: Source<'_, Expr>,
75 aggregates: &dyn AggregateClassifier,
76 subqueries: &mut Vec<QueryPlan>,
77 ) -> Result<ScalarExpr> {
78 self.check()?;
79 Ok(match expression.node() {
80 Node::Star => ScalarExpr::Star,
81 Node::QualifiedStar(name) => ScalarExpr::QualifiedStar(self.text(name)?),
82 Node::Default => ScalarExpr::Default,
83 Node::Column(name) => ScalarExpr::Column(self.text(name)?),
84 Node::QualifiedColumn { qualifier, column } => ScalarExpr::QualifiedColumn {
85 qualifier: self.text(qualifier)?,
86 column: self.text(column)?,
87 },
88 Node::InternalColumn(column) => ScalarExpr::InternalColumn(column),
89 Node::Literal(value) => ScalarExpr::Literal(self.value(value)?),
90 Node::TypedLiteral {
91 value,
92 ty,
93 composite_source,
94 } => ScalarExpr::TypedLiteral {
95 composite_source: self.composite_source(composite_source)?,
96 value: self.value(value)?,
97 ty: self.text(ty)?,
98 bound_type: None,
99 parameter_index: None,
100 },
101 Node::Param(index) => ScalarExpr::Param(index),
102 Node::Func {
103 name,
104 binding,
105 args,
106 distinct,
107 order_by,
108 order_syntax,
109 filter,
110 } => ScalarExpr::Func {
111 name: self.text(name)?,
112 binding: binding.map(|binding| self.binding(binding)).transpose()?,
113 args: self.map(args, |this, argument| {
114 this.expression(argument, aggregates, subqueries)
115 })?,
116 distinct,
117 order_by: self.map(order_by, |this, order| {
118 this.order(order, aggregates, subqueries)
119 })?,
120 order_syntax,
121 filter: filter
122 .map(|filter| self.child(filter, aggregates, subqueries))
123 .transpose()?,
124 },
125 Node::Array(items) => ScalarExpr::Array(self.map(items, |this, item| {
126 this.expression(item, aggregates, subqueries)
127 })?),
128 Node::CompositeRow { items, binding } => ScalarExpr::CompositeRow {
129 items: self.map(items, |this, item| {
130 this.expression(item, aggregates, subqueries)
131 })?,
132 binding: self.composite_binding(binding)?,
133 bound_type: None,
134 },
135 Node::Row(items) => ScalarExpr::Row(self.map(items, |this, item| {
136 this.expression(item, aggregates, subqueries)
137 })?),
138 Node::Binary { op, lhs, rhs } => ScalarExpr::Binary {
139 op,
140 lhs: self.child(lhs, aggregates, subqueries)?,
141 rhs: self.child(rhs, aggregates, subqueries)?,
142 },
143 Node::UnaryMinus(expression) => {
144 ScalarExpr::UnaryMinus(self.child(expression, aggregates, subqueries)?)
145 }
146 Node::Not(expression) => {
147 ScalarExpr::Not(self.child(expression, aggregates, subqueries)?)
148 }
149 Node::And(items) => ScalarExpr::And(self.map(items, |this, item| {
150 this.expression(item, aggregates, subqueries)
151 })?),
152 Node::Or(items) => ScalarExpr::Or(self.map(items, |this, item| {
153 this.expression(item, aggregates, subqueries)
154 })?),
155 Node::IsNull { expr, negated } => ScalarExpr::IsNull {
156 expr: self.child(expr, aggregates, subqueries)?,
157 negated,
158 },
159 Node::Between { expr, low, high } => ScalarExpr::Between {
160 expr: self.child(expr, aggregates, subqueries)?,
161 low: self.child(low, aggregates, subqueries)?,
162 high: self.child(high, aggregates, subqueries)?,
163 },
164 Node::InList {
165 expr,
166 list,
167 negated,
168 } => ScalarExpr::InList {
169 expr: self.child(expr, aggregates, subqueries)?,
170 list: self.map(list, |this, item| {
171 this.expression(item, aggregates, subqueries)
172 })?,
173 negated,
174 },
175 Node::WindowCall {
176 name,
177 args,
178 spec,
179 filter,
180 modifiers,
181 } => ScalarExpr::WindowCall {
182 modifiers,
183 name: self.text(name)?,
184 args: self.map(args, |this, argument| {
185 this.expression(argument, aggregates, subqueries)
186 })?,
187 spec: self.window(spec, aggregates, subqueries)?,
188 filter: filter
189 .map(|filter| self.child(filter, aggregates, subqueries))
190 .transpose()?,
191 },
192 Node::Case {
193 base,
194 when,
195 else_branch,
196 } => ScalarExpr::Case {
197 base: base
198 .map(|base| self.child(base, aggregates, subqueries))
199 .transpose()?,
200 when: self.map(when, |this, pair| {
201 let (condition, result) = pair.pair();
202 Ok((
203 this.expression(condition, aggregates, subqueries)?,
204 this.expression(result, aggregates, subqueries)?,
205 ))
206 })?,
207 else_branch: else_branch
208 .map(|branch| self.child(branch, aggregates, subqueries))
209 .transpose()?,
210 },
211 Node::Cast { expr, ty, implicit } => ScalarExpr::Cast {
212 implicit,
213 expr: self.child(expr, aggregates, subqueries)?,
214 ty: self.text(ty)?,
215 },
216 Node::ScalarSubquery(query) => {
217 ScalarExpr::ScalarSubquery(self.query(query, aggregates, subqueries)?)
218 }
219 Node::Exists { body, negated } => ScalarExpr::Exists {
220 subquery: self.query(body, aggregates, subqueries)?,
221 negated,
222 },
223 Node::InSubquery {
224 expr,
225 body,
226 negated,
227 } => {
228 let expr = self.child(expr, aggregates, subqueries)?;
229 ScalarExpr::InSubquery {
230 expr,
231 subquery: self.query(body, aggregates, subqueries)?,
232 negated,
233 }
234 }
235 })
236 }
237
238 fn child(
239 &mut self,
240 expression: Source<'_, Box<Expr>>,
241 aggregates: &dyn AggregateClassifier,
242 subqueries: &mut Vec<QueryPlan>,
243 ) -> Result<Box<ScalarExpr>> {
244 self.boxed(|this| this.expression(expression.unbox(), aggregates, subqueries))
245 }
246
247 fn query(
248 &self,
249 query: Source<'_, Box<crate::ast::SelectStmt>>,
250 aggregates: &dyn AggregateClassifier,
251 subqueries: &mut Vec<QueryPlan>,
252 ) -> Result<usize> {
253 self.check()?;
254 let Source::Owned(query) = query else {
255 return Err(CatalogRetentionError::UnexpectedSubquery);
256 };
257 let id = subqueries.len();
258 subqueries.push(QueryPlan::lower_with(*query, aggregates));
259 Ok(id)
260 }
261}
262
263pub(crate) fn is_builtin_aggregate(name: &str) -> bool {
264 crate::ast::is_builtin_aggregate_function(name)
265}
266
267#[cfg(test)]
268mod tests;