1use super::rewrite::rewrite_query_scalars;
10use super::scalar::{is_builtin_aggregate, lower_scalar_expression};
11use super::{
12 AccessPathPlan, AggregateClassifier, AssignmentPlan, ComputePlan, CteCyclePlan, CtePlan,
13 CteSearchPlan, Expr, ExpressionPlan, FromClause, JoinExecutionStrategy, MergeWhenPlan,
14 NoRegisteredAggregates, OrderBy, OrderPlan, Projection, ProjectionPlan, QueryBlockPlan,
15 QueryPlan, RelationalPlan, ScalarExpr, SelectStmt, SourcePlan, TableFunctionPlan, CTE,
16};
17
18impl QueryPlan {
19 pub fn rewrite_scalar_expressions(&mut self, rewrite: &mut dyn FnMut(&mut ScalarExpr)) {
22 rewrite_query_scalars(self, rewrite);
23 }
24
25 #[must_use]
26 pub fn lower(statement: SelectStmt) -> Self {
27 Self::lower_with(statement, &NoRegisteredAggregates)
28 }
29
30 #[must_use]
31 pub fn lower_with(mut statement: SelectStmt, aggregates: &dyn AggregateClassifier) -> Self {
32 let ctes = lower_ctes(&statement.with, aggregates);
33 statement.with.clear();
34 let root = lower_relational_root(statement, aggregates);
35 Self {
36 relations_bound: false,
37 ctes,
38 root,
39 }
40 }
41}
42
43pub(super) fn lower_ctes(ctes: &[CTE], aggregates: &dyn AggregateClassifier) -> Vec<CtePlan> {
44 ctes.iter()
45 .map(|cte| CtePlan {
46 name: cte.name.clone(),
47 columns: cte.columns.clone(),
48 recursive: cte.recursive,
49 materialization: cte.materialization,
50 search: cte.search.as_ref().map(|search| CteSearchPlan {
51 columns: search.columns.clone(),
52 breadth_first: search.breadth_first,
53 sequence_column: search.sequence_column.clone(),
54 }),
55 cycle: cte.cycle.as_ref().map(|cycle| CteCyclePlan {
56 columns: cycle.columns.clone(),
57 mark_column: cycle.mark_column.clone(),
58 mark_value: lower_scalar_expression(
59 cycle.mark_value.clone(),
60 aggregates,
61 &mut Vec::new(),
62 ),
63 mark_default: lower_scalar_expression(
64 cycle.mark_default.clone(),
65 aggregates,
66 &mut Vec::new(),
67 ),
68 path_column: cycle.path_column.clone(),
69 }),
70 query: Box::new(QueryPlan::lower_with((*cte.query).clone(), aggregates)),
71 })
72 .collect()
73}
74
75pub(super) fn lower_assignments(
76 assignments: Vec<(String, Expr)>,
77 aggregates: &dyn AggregateClassifier,
78 subqueries: &mut Vec<QueryPlan>,
79) -> Vec<AssignmentPlan> {
80 assignments
81 .into_iter()
82 .map(|(column, expression)| AssignmentPlan {
83 column,
84 value: lower_scalar_expression(expression, aggregates, subqueries),
85 })
86 .collect()
87}
88
89pub(super) fn lower_merge_when(
90 clause: uqa_sql::ast::MergeWhen,
91 aggregates: &dyn AggregateClassifier,
92 subqueries: &mut Vec<QueryPlan>,
93) -> MergeWhenPlan {
94 let mut lower_optional = |expression: Option<Expr>| {
95 expression.map(|expression| lower_scalar_expression(expression, aggregates, subqueries))
96 };
97 match clause {
98 uqa_sql::ast::MergeWhen::UpdateMatched {
99 condition,
100 assignments,
101 } => {
102 let condition = lower_optional(condition);
103 let assignments = lower_assignments(assignments, aggregates, subqueries);
104 MergeWhenPlan::UpdateMatched {
105 condition,
106 assignments,
107 }
108 }
109 uqa_sql::ast::MergeWhen::DeleteMatched { condition } => MergeWhenPlan::DeleteMatched {
110 condition: lower_optional(condition),
111 },
112 uqa_sql::ast::MergeWhen::UpdateNotMatchedBySource {
113 condition,
114 assignments,
115 } => {
116 let condition = lower_optional(condition);
117 let assignments = lower_assignments(assignments, aggregates, subqueries);
118 MergeWhenPlan::UpdateNotMatchedBySource {
119 condition,
120 assignments,
121 }
122 }
123 uqa_sql::ast::MergeWhen::DeleteNotMatchedBySource { condition } => {
124 MergeWhenPlan::DeleteNotMatchedBySource {
125 condition: lower_optional(condition),
126 }
127 }
128 uqa_sql::ast::MergeWhen::InsertNotMatched {
129 condition,
130 columns,
131 values,
132 } => {
133 let condition = lower_optional(condition);
134 let values = values
135 .into_iter()
136 .map(|value| lower_scalar_expression(value, aggregates, subqueries))
137 .collect();
138 MergeWhenPlan::InsertNotMatched {
139 condition,
140 columns,
141 values,
142 }
143 }
144 uqa_sql::ast::MergeWhen::NothingMatched { condition } => MergeWhenPlan::NothingMatched {
145 condition: lower_optional(condition),
146 },
147 uqa_sql::ast::MergeWhen::NothingNotMatched { condition } => {
148 MergeWhenPlan::NothingNotMatched {
149 condition: lower_optional(condition),
150 }
151 }
152 uqa_sql::ast::MergeWhen::NothingNotMatchedBySource { condition } => {
153 MergeWhenPlan::NothingNotMatchedBySource {
154 condition: lower_optional(condition),
155 }
156 }
157 }
158}
159pub(super) fn lower_relational_root(
160 mut statement: SelectStmt,
161 aggregates: &dyn AggregateClassifier,
162) -> RelationalPlan {
163 if statement.set_op.is_none() && !statement.values.is_empty() {
164 let mut subqueries = Vec::new();
165 let rows = statement
166 .values
167 .into_iter()
168 .map(|row| {
169 row.into_iter()
170 .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries))
171 .collect()
172 })
173 .collect();
174 return RelationalPlan::Values { rows, subqueries };
175 }
176 let Some(set_op) = statement.set_op.take() else {
177 return RelationalPlan::QueryBlock(Box::new(QueryBlockPlan::lower_with(
178 statement, aggregates,
179 )));
180 };
181
182 let left = if let Some(left) = set_op.left {
183 QueryPlan::lower_with(*left, aggregates)
184 } else {
185 QueryPlan {
186 relations_bound: false,
187 ctes: Vec::new(),
188 root: RelationalPlan::QueryBlock(Box::new(QueryBlockPlan::lower_with(
189 statement, aggregates,
190 ))),
191 }
192 };
193 let right = QueryPlan::lower_with(set_op.right, aggregates);
194 let mut subqueries = Vec::new();
195 RelationalPlan::SetOp {
196 kind: set_op.kind,
197 all: set_op.all,
198 left: Box::new(left),
199 right: Box::new(right),
200 order_by: set_op
201 .combined_order_by
202 .into_iter()
203 .map(|order| OrderPlan::lower_with(order, aggregates, &mut subqueries))
204 .collect(),
205 limit: set_op
206 .combined_limit
207 .map(|expr| Box::new(lower_scalar_expression(expr, aggregates, &mut subqueries))),
208 with_ties: set_op.combined_with_ties,
209 offset: set_op
210 .combined_offset
211 .map(|expr| Box::new(lower_scalar_expression(expr, aggregates, &mut subqueries))),
212 subqueries,
213 }
214}
215
216impl QueryBlockPlan {
217 fn lower_with(statement: SelectStmt, aggregates: &dyn AggregateClassifier) -> Self {
218 debug_assert!(statement.with.is_empty());
219 debug_assert!(statement.set_op.is_none());
220 let mut subqueries = Vec::new();
221 let projections: Vec<ProjectionPlan> = statement
222 .projections
223 .into_iter()
224 .map(|projection| ProjectionPlan::lower_with(projection, aggregates, &mut subqueries))
225 .collect();
226 let is_aggregate =
227 |name: &str| is_builtin_aggregate(name) || aggregates.is_registered_aggregate(name);
228 let has_aggregate = !statement.group_by.is_empty()
229 || !statement.grouping_sets.is_empty()
230 || statement.having.is_some()
231 || projections
232 .iter()
233 .any(|projection| projection.expr.contains_aggregate(&is_aggregate));
234 let has_window = projections
235 .iter()
236 .any(|projection| projection.expr.contains_window());
237 let compute = if has_aggregate {
238 ComputePlan::Aggregate
239 } else if has_window {
240 ComputePlan::Window
241 } else {
242 ComputePlan::Project
243 };
244 Self {
245 projections,
246 from: statement
247 .from
248 .map(|source| SourcePlan::lower_with(source, aggregates, &mut subqueries)),
249 r#where: statement
250 .r#where
251 .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries)),
252 compute,
253 group_by: statement
254 .group_by
255 .into_iter()
256 .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries))
257 .collect(),
258 grouping_sets: statement
259 .grouping_sets
260 .into_iter()
261 .map(|set| {
262 set.into_iter()
263 .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries))
264 .collect()
265 })
266 .collect(),
267 group_distinct: statement.group_distinct,
268 having: statement
269 .having
270 .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries)),
271 order_by: statement
272 .order_by
273 .into_iter()
274 .map(|order| OrderPlan::lower_with(order, aggregates, &mut subqueries))
275 .collect(),
276 limit: statement
277 .limit
278 .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries)),
279 with_ties: statement.with_ties,
280 offset: statement
281 .offset
282 .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries)),
283 distinct: statement.distinct,
284 distinct_on: statement
285 .distinct_on
286 .into_iter()
287 .map(|expr| lower_scalar_expression(expr, aggregates, &mut subqueries))
288 .collect(),
289 subqueries,
290 access: AccessPathPlan::Row,
291 locking: statement.locking,
292 }
293 }
294
295 #[must_use]
299 pub fn expressions(&self) -> Vec<&ScalarExpr> {
300 let mut expressions = Vec::new();
301 if let Some(source) = &self.from {
302 source.push_expressions(&mut expressions);
303 }
304 if let Some(filter) = &self.r#where {
305 expressions.push(filter);
306 }
307 for projection in &self.projections {
308 expressions.push(&projection.expr);
309 }
310 expressions.extend(&self.group_by);
311 for set in &self.grouping_sets {
312 expressions.extend(set);
313 }
314 if let Some(having) = &self.having {
315 expressions.push(having);
316 }
317 expressions.extend(self.order_by.iter().map(|order| &order.expr));
318 if let Some(limit) = &self.limit {
319 expressions.push(limit);
320 }
321 if let Some(offset) = &self.offset {
322 expressions.push(offset);
323 }
324 expressions.extend(&self.distinct_on);
325 expressions
326 }
327}
328
329impl SourcePlan {
330 #[must_use]
332 pub fn visible_qualifier(&self) -> Option<&str> {
333 match self {
334 Self::Table {
335 qualifier, alias, ..
336 } => Some(alias.as_deref().unwrap_or(qualifier)),
337 Self::Function {
338 output_name, alias, ..
339 } => Some(alias.as_deref().unwrap_or(output_name)),
340 Self::FunctionGroup {
341 functions, alias, ..
342 } => alias.as_deref().or_else(|| {
343 functions
344 .first()
345 .map(|function| function.output_name.as_str())
346 }),
347 Self::Values {
348 alias,
349 internal_relation,
350 ..
351 } => internal_relation
352 .is_none()
353 .then_some(alias.as_deref())
354 .flatten(),
355 Self::Subquery { alias, .. } => alias.as_deref(),
356 Self::Join { alias, .. } => alias.as_deref(),
357 }
358 }
359
360 #[expect(
361 clippy::too_many_lines,
362 reason = "plan lowering preserves exhaustive variants and structural identities"
363 )]
364 pub(super) fn lower_with(
365 source: FromClause,
366 aggregates: &dyn AggregateClassifier,
367 subqueries: &mut Vec<QueryPlan>,
368 ) -> Self {
369 match source {
370 FromClause::Table {
371 name,
372 qualifier,
373 alias,
374 column_aliases,
375 include_descendants,
376 } => Self::Table {
377 name,
378 qualifier,
379 alias,
380 column_aliases,
381 include_descendants,
382 },
383 FromClause::Join {
384 left,
385 right,
386 kind,
387 on,
388 using,
389 natural,
390 alias,
391 column_aliases,
392 lateral,
393 } => Self::Join {
394 left: Box::new(Self::lower_with(*left, aggregates, subqueries)),
395 right: Box::new(Self::lower_with(*right, aggregates, subqueries)),
396 kind,
397 on: on.map(|expr| lower_scalar_expression(expr, aggregates, subqueries)),
398 using,
399 natural,
400 alias,
401 column_aliases,
402 lateral,
403 strategy: JoinExecutionStrategy::Auto,
404 },
405 FromClause::Values {
406 rows,
407 alias,
408 column_aliases,
409 internal_relation,
410 internal_column_types,
411 } => Self::Values {
412 rows: rows
413 .into_iter()
414 .map(|row| {
415 row.into_iter()
416 .map(|expr| lower_scalar_expression(expr, aggregates, subqueries))
417 .collect()
418 })
419 .collect(),
420 alias,
421 column_aliases,
422 internal_relation,
423 internal_column_types,
424 },
425 FromClause::Function {
426 name,
427 binding,
428 output_name,
429 relations,
430 args,
431 alias,
432 column_aliases,
433 ordinality,
434 column_types,
435 } => Self::Function {
436 name,
437 binding,
438 output_name,
439 relations,
440 args: args
441 .into_iter()
442 .map(|expr| lower_scalar_expression(expr, aggregates, subqueries))
443 .collect(),
444 alias,
445 column_aliases,
446 ordinality,
447 column_types,
448 },
449 FromClause::FunctionGroup {
450 functions,
451 alias,
452 column_aliases,
453 ordinality,
454 } => Self::FunctionGroup {
455 functions: functions
456 .into_iter()
457 .map(|function| TableFunctionPlan {
458 name: function.name,
459 binding: function.binding,
460 output_name: function.output_name,
461 relations: function.relations,
462 args: function
463 .args
464 .into_iter()
465 .map(|expr| lower_scalar_expression(expr, aggregates, subqueries))
466 .collect(),
467 column_aliases: function.column_aliases,
468 column_types: function.column_types,
469 })
470 .collect(),
471 alias,
472 column_aliases,
473 ordinality,
474 },
475 FromClause::Subquery {
476 body,
477 alias,
478 column_aliases,
479 } => Self::Subquery {
480 body: Box::new(QueryPlan::lower_with(*body, aggregates)),
481 alias,
482 column_aliases,
483 },
484 }
485 }
486
487 fn push_expressions<'a>(&'a self, output: &mut Vec<&'a ScalarExpr>) {
488 match self {
489 Self::Table { .. } | Self::Subquery { .. } => {}
490 Self::Join {
491 left, right, on, ..
492 } => {
493 left.push_expressions(output);
494 right.push_expressions(output);
495 if let Some(on) = on {
496 output.push(on);
497 }
498 }
499 Self::Values { rows, .. } => {
500 for row in rows {
501 output.extend(row);
502 }
503 }
504 Self::Function { args, .. } => output.extend(args),
505 Self::FunctionGroup { functions, .. } => {
506 for function in functions {
507 output.extend(&function.args);
508 }
509 }
510 }
511 }
512
513 pub fn collect_tables(&self, output: &mut Vec<(String, Option<String>)>) {
514 match self {
515 Self::Table {
516 name,
517 qualifier,
518 alias,
519 ..
520 } => output.push((
521 name.clone(),
522 Some(alias.as_ref().unwrap_or(qualifier).clone()),
523 )),
524 Self::Join { left, right, .. } => {
525 left.collect_tables(output);
526 right.collect_tables(output);
527 }
528 Self::Values { .. }
529 | Self::Function { .. }
530 | Self::FunctionGroup { .. }
531 | Self::Subquery { .. } => {}
532 }
533 }
534}
535
536impl ProjectionPlan {
537 pub(super) fn lower_with(
538 projection: Projection,
539 aggregates: &dyn AggregateClassifier,
540 subqueries: &mut Vec<QueryPlan>,
541 ) -> Self {
542 Self {
543 expr: lower_scalar_expression(projection.expr, aggregates, subqueries),
544 alias: projection.alias,
545 }
546 }
547}
548
549impl OrderPlan {
550 fn lower_with(
551 order: OrderBy,
552 aggregates: &dyn AggregateClassifier,
553 subqueries: &mut Vec<QueryPlan>,
554 ) -> Self {
555 Self {
556 expr: lower_scalar_expression(order.expr, aggregates, subqueries),
557 descending: order.descending,
558 nulls: order.nulls,
559 }
560 }
561}
562
563impl ExpressionPlan {
564 #[must_use]
565 pub fn lower(expression: Expr) -> Self {
566 Self::lower_with(expression, &NoRegisteredAggregates)
567 }
568
569 pub fn lower_with(expression: Expr, aggregates: &dyn AggregateClassifier) -> Self {
570 let mut subqueries = Vec::new();
571 let scalar = lower_scalar_expression(expression, aggregates, &mut subqueries);
572 Self { scalar, subqueries }
573 }
574}