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 { value, ty } => ScalarExpr::TypedLiteral {
91 value: self.value(value)?,
92 ty: self.text(ty)?,
93 bound_type: None,
94 parameter_index: None,
95 },
96 Node::Param(index) => ScalarExpr::Param(index),
97 Node::Func {
98 name,
99 binding,
100 args,
101 distinct,
102 order_by,
103 order_syntax,
104 filter,
105 } => ScalarExpr::Func {
106 name: self.text(name)?,
107 binding: binding.map(|binding| self.binding(binding)).transpose()?,
108 args: self.map(args, |this, argument| {
109 this.expression(argument, aggregates, subqueries)
110 })?,
111 distinct,
112 order_by: self.map(order_by, |this, order| {
113 this.order(order, aggregates, subqueries)
114 })?,
115 order_syntax,
116 filter: filter
117 .map(|filter| self.child(filter, aggregates, subqueries))
118 .transpose()?,
119 },
120 Node::Array(items) => ScalarExpr::Array(self.map(items, |this, item| {
121 this.expression(item, aggregates, subqueries)
122 })?),
123 Node::CompositeRow { items, binding } => ScalarExpr::CompositeRow {
124 items: self.map(items, |this, item| {
125 this.expression(item, aggregates, subqueries)
126 })?,
127 binding: self.composite_binding(binding)?,
128 bound_type: None,
129 },
130 Node::Row(items) => ScalarExpr::Row(self.map(items, |this, item| {
131 this.expression(item, aggregates, subqueries)
132 })?),
133 Node::Binary { op, lhs, rhs } => ScalarExpr::Binary {
134 op,
135 lhs: self.child(lhs, aggregates, subqueries)?,
136 rhs: self.child(rhs, aggregates, subqueries)?,
137 },
138 Node::UnaryMinus(expression) => {
139 ScalarExpr::UnaryMinus(self.child(expression, aggregates, subqueries)?)
140 }
141 Node::Not(expression) => {
142 ScalarExpr::Not(self.child(expression, aggregates, subqueries)?)
143 }
144 Node::And(items) => ScalarExpr::And(self.map(items, |this, item| {
145 this.expression(item, aggregates, subqueries)
146 })?),
147 Node::Or(items) => ScalarExpr::Or(self.map(items, |this, item| {
148 this.expression(item, aggregates, subqueries)
149 })?),
150 Node::IsNull { expr, negated } => ScalarExpr::IsNull {
151 expr: self.child(expr, aggregates, subqueries)?,
152 negated,
153 },
154 Node::Between { expr, low, high } => ScalarExpr::Between {
155 expr: self.child(expr, aggregates, subqueries)?,
156 low: self.child(low, aggregates, subqueries)?,
157 high: self.child(high, aggregates, subqueries)?,
158 },
159 Node::InList {
160 expr,
161 list,
162 negated,
163 } => ScalarExpr::InList {
164 expr: self.child(expr, aggregates, subqueries)?,
165 list: self.map(list, |this, item| {
166 this.expression(item, aggregates, subqueries)
167 })?,
168 negated,
169 },
170 Node::WindowCall {
171 name,
172 args,
173 spec,
174 filter,
175 modifiers,
176 } => ScalarExpr::WindowCall {
177 modifiers,
178 name: self.text(name)?,
179 args: self.map(args, |this, argument| {
180 this.expression(argument, aggregates, subqueries)
181 })?,
182 spec: self.window(spec, aggregates, subqueries)?,
183 filter: filter
184 .map(|filter| self.child(filter, aggregates, subqueries))
185 .transpose()?,
186 },
187 Node::Case {
188 base,
189 when,
190 else_branch,
191 } => ScalarExpr::Case {
192 base: base
193 .map(|base| self.child(base, aggregates, subqueries))
194 .transpose()?,
195 when: self.map(when, |this, pair| {
196 let (condition, result) = pair.pair();
197 Ok((
198 this.expression(condition, aggregates, subqueries)?,
199 this.expression(result, aggregates, subqueries)?,
200 ))
201 })?,
202 else_branch: else_branch
203 .map(|branch| self.child(branch, aggregates, subqueries))
204 .transpose()?,
205 },
206 Node::Cast { expr, ty, implicit } => ScalarExpr::Cast {
207 implicit,
208 expr: self.child(expr, aggregates, subqueries)?,
209 ty: self.text(ty)?,
210 },
211 Node::ScalarSubquery(query) => {
212 ScalarExpr::ScalarSubquery(self.query(query, aggregates, subqueries)?)
213 }
214 Node::Exists { body, negated } => ScalarExpr::Exists {
215 subquery: self.query(body, aggregates, subqueries)?,
216 negated,
217 },
218 Node::InSubquery {
219 expr,
220 body,
221 negated,
222 } => {
223 let expr = self.child(expr, aggregates, subqueries)?;
224 ScalarExpr::InSubquery {
225 expr,
226 subquery: self.query(body, aggregates, subqueries)?,
227 negated,
228 }
229 }
230 })
231 }
232
233 fn child(
234 &mut self,
235 expression: Source<'_, Box<Expr>>,
236 aggregates: &dyn AggregateClassifier,
237 subqueries: &mut Vec<QueryPlan>,
238 ) -> Result<Box<ScalarExpr>> {
239 self.boxed(|this| this.expression(expression.unbox(), aggregates, subqueries))
240 }
241
242 fn query(
243 &self,
244 query: Source<'_, Box<crate::ast::SelectStmt>>,
245 aggregates: &dyn AggregateClassifier,
246 subqueries: &mut Vec<QueryPlan>,
247 ) -> Result<usize> {
248 self.check()?;
249 let Source::Owned(query) = query else {
250 return Err(CatalogRetentionError::UnexpectedSubquery);
251 };
252 let id = subqueries.len();
253 subqueries.push(QueryPlan::lower_with(*query, aggregates));
254 Ok(id)
255 }
256}
257
258pub(crate) fn is_builtin_aggregate(name: &str) -> bool {
259 crate::ast::is_builtin_aggregate_function(name)
260}
261
262#[cfg(test)]
263mod tests;