1use crate::plan::{ProjectionPlan, QueryBlockPlan};
10use crate::{FunctionTypeResolver, RowSchema, ScalarExpr, ScalarFrameBound};
11use crate::{SQLError, SQLParam};
12use uqa_core::Value;
13
14use super::validation::{
15 expression_may_return_set, function_may_return_set, resolve_set_function_binding,
16};
17use super::{
18 AggregateOutputProjectionPlan, GroupSetProjectionPlan, ProjectionTarget, SetFunctionCall,
19 SetFunctionCatalog,
20};
21use crate::semantics::aggregates::{exprs_match, is_aggregate};
22use crate::semantics::projection_columns;
23
24#[expect(
25 clippy::too_many_lines,
26 reason = "preserves SELECT schema and row identity"
27)]
28pub fn rewrite_set_calls(
29 engine: &dyn SetFunctionCatalog,
30 resolver: &dyn FunctionTypeResolver,
31 mut expression: ScalarExpr,
32 calls: &mut Vec<SetFunctionCall>,
33 call_relation: crate::ast::InternalRelationId,
34 schema: &RowSchema,
35 params: &[SQLParam],
36) -> Result<ScalarExpr, SQLError> {
37 let descendant_start = calls.len();
38 match &mut expression {
39 ScalarExpr::Func {
40 args,
41 order_by,
42 filter,
43 ..
44 } => {
45 for argument in args {
46 *argument = rewrite_set_calls(
47 engine,
48 resolver,
49 argument.clone(),
50 calls,
51 call_relation,
52 schema,
53 params,
54 )?;
55 }
56 for order in order_by {
57 order.expr = rewrite_set_calls(
58 engine,
59 resolver,
60 order.expr.clone(),
61 calls,
62 call_relation,
63 schema,
64 params,
65 )?;
66 }
67 if let Some(filter) = filter {
68 **filter = rewrite_set_calls(
69 engine,
70 resolver,
71 (**filter).clone(),
72 calls,
73 call_relation,
74 schema,
75 params,
76 )?;
77 }
78 }
79 ScalarExpr::Array(items)
80 | ScalarExpr::Row(items)
81 | ScalarExpr::And(items)
82 | ScalarExpr::Or(items) => {
83 for item in items {
84 *item = rewrite_set_calls(
85 engine,
86 resolver,
87 item.clone(),
88 calls,
89 call_relation,
90 schema,
91 params,
92 )?;
93 }
94 }
95 ScalarExpr::Binary { lhs, rhs, .. } => {
96 **lhs = rewrite_set_calls(
97 engine,
98 resolver,
99 (**lhs).clone(),
100 calls,
101 call_relation,
102 schema,
103 params,
104 )?;
105 **rhs = rewrite_set_calls(
106 engine,
107 resolver,
108 (**rhs).clone(),
109 calls,
110 call_relation,
111 schema,
112 params,
113 )?;
114 }
115 ScalarExpr::Not(inner)
116 | ScalarExpr::UnaryMinus(inner)
117 | ScalarExpr::IsNull { expr: inner, .. }
118 | ScalarExpr::Cast { expr: inner, .. } => {
119 **inner = rewrite_set_calls(
120 engine,
121 resolver,
122 (**inner).clone(),
123 calls,
124 call_relation,
125 schema,
126 params,
127 )?;
128 }
129 ScalarExpr::Between { expr, low, high } => {
130 **expr = rewrite_set_calls(
131 engine,
132 resolver,
133 (**expr).clone(),
134 calls,
135 call_relation,
136 schema,
137 params,
138 )?;
139 **low = rewrite_set_calls(
140 engine,
141 resolver,
142 (**low).clone(),
143 calls,
144 call_relation,
145 schema,
146 params,
147 )?;
148 **high = rewrite_set_calls(
149 engine,
150 resolver,
151 (**high).clone(),
152 calls,
153 call_relation,
154 schema,
155 params,
156 )?;
157 }
158 ScalarExpr::InList { expr, list, .. } => {
159 **expr = rewrite_set_calls(
160 engine,
161 resolver,
162 (**expr).clone(),
163 calls,
164 call_relation,
165 schema,
166 params,
167 )?;
168 for item in list {
169 *item = rewrite_set_calls(
170 engine,
171 resolver,
172 item.clone(),
173 calls,
174 call_relation,
175 schema,
176 params,
177 )?;
178 }
179 }
180 ScalarExpr::WindowCall { args, spec, .. } => {
181 for argument in args {
182 *argument = rewrite_set_calls(
183 engine,
184 resolver,
185 argument.clone(),
186 calls,
187 call_relation,
188 schema,
189 params,
190 )?;
191 }
192 for item in &mut spec.partition_by {
193 *item = rewrite_set_calls(
194 engine,
195 resolver,
196 item.clone(),
197 calls,
198 call_relation,
199 schema,
200 params,
201 )?;
202 }
203 for order in &mut spec.order_by {
204 order.expr = rewrite_set_calls(
205 engine,
206 resolver,
207 order.expr.clone(),
208 calls,
209 call_relation,
210 schema,
211 params,
212 )?;
213 }
214 if let Some(frame) = &mut spec.frame {
215 rewrite_set_frame_bound(
216 engine,
217 resolver,
218 &mut frame.start,
219 calls,
220 call_relation,
221 schema,
222 params,
223 )?;
224 rewrite_set_frame_bound(
225 engine,
226 resolver,
227 &mut frame.end,
228 calls,
229 call_relation,
230 schema,
231 params,
232 )?;
233 }
234 }
235 ScalarExpr::Case {
236 base,
237 when,
238 else_branch,
239 } => {
240 if let Some(base) = base {
241 **base = rewrite_set_calls(
242 engine,
243 resolver,
244 (**base).clone(),
245 calls,
246 call_relation,
247 schema,
248 params,
249 )?;
250 }
251 for (condition, result) in when {
252 *condition = rewrite_set_calls(
253 engine,
254 resolver,
255 condition.clone(),
256 calls,
257 call_relation,
258 schema,
259 params,
260 )?;
261 *result = rewrite_set_calls(
262 engine,
263 resolver,
264 result.clone(),
265 calls,
266 call_relation,
267 schema,
268 params,
269 )?;
270 }
271 if let Some(branch) = else_branch {
272 **branch = rewrite_set_calls(
273 engine,
274 resolver,
275 (**branch).clone(),
276 calls,
277 call_relation,
278 schema,
279 params,
280 )?;
281 }
282 }
283 ScalarExpr::InSubquery { expr, .. } => {
284 **expr = rewrite_set_calls(
285 engine,
286 resolver,
287 (**expr).clone(),
288 calls,
289 call_relation,
290 schema,
291 params,
292 )?;
293 }
294 ScalarExpr::Default
295 | ScalarExpr::Star
296 | ScalarExpr::QualifiedStar(_)
297 | ScalarExpr::Column(_)
298 | ScalarExpr::Position(_)
299 | ScalarExpr::InternalColumn(_)
300 | ScalarExpr::QualifiedColumn { .. }
301 | ScalarExpr::Literal(_)
302 | ScalarExpr::TypedLiteral { .. }
303 | ScalarExpr::Param(_)
304 | ScalarExpr::ScalarSubquery(_)
305 | ScalarExpr::Exists { .. } => {}
306 }
307 if let ScalarExpr::Func {
308 name,
309 binding,
310 args,
311 ..
312 } = &expression
313 {
314 if function_may_return_set(
315 engine,
316 resolver,
317 name,
318 binding.as_ref(),
319 args,
320 schema,
321 params,
322 )? {
323 let binding = resolve_set_function_binding(
324 engine,
325 resolver,
326 name,
327 binding.as_ref(),
328 args,
329 schema,
330 params,
331 )?
332 .or_else(|| binding.clone());
333 let level = calls[descendant_start..]
334 .iter()
335 .map(|call| call.level + 1)
336 .max()
337 .unwrap_or(0);
338 let placeholder = call_relation.column(calls.len());
339 calls.push(SetFunctionCall {
340 placeholder,
341 name: name.clone(),
342 binding,
343 args: args.clone(),
344 level,
345 });
346 return Ok(ScalarExpr::InternalColumn(placeholder));
347 }
348 }
349 Ok(expression)
350}
351
352fn rewrite_set_frame_bound(
353 engine: &dyn SetFunctionCatalog,
354 resolver: &dyn FunctionTypeResolver,
355 bound: &mut ScalarFrameBound,
356 calls: &mut Vec<SetFunctionCall>,
357 call_relation: crate::ast::InternalRelationId,
358 schema: &RowSchema,
359 params: &[SQLParam],
360) -> Result<(), SQLError> {
361 match bound {
362 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
363 **expression = rewrite_set_calls(
364 engine,
365 resolver,
366 (**expression).clone(),
367 calls,
368 call_relation,
369 schema,
370 params,
371 )?;
372 }
373 ScalarFrameBound::UnboundedPreceding
374 | ScalarFrameBound::UnboundedFollowing
375 | ScalarFrameBound::CurrentRow => {}
376 }
377 Ok(())
378}
379
380fn replace_group_set_expression(
381 expression: &mut ScalarExpr,
382 mappings: &[(ScalarExpr, crate::ast::InternalColumnRef)],
383) {
384 if let Some((_, column)) = mappings
385 .iter()
386 .find(|(group, _)| exprs_match(expression, group))
387 {
388 *expression = ScalarExpr::InternalColumn(*column);
389 return;
390 }
391 match expression {
392 ScalarExpr::Func {
393 args,
394 order_by,
395 filter,
396 ..
397 } => {
398 for argument in args {
399 replace_group_set_expression(argument, mappings);
400 }
401 for order in order_by {
402 replace_group_set_expression(&mut order.expr, mappings);
403 }
404 if let Some(filter) = filter {
405 replace_group_set_expression(filter, mappings);
406 }
407 }
408 ScalarExpr::Array(items)
409 | ScalarExpr::Row(items)
410 | ScalarExpr::And(items)
411 | ScalarExpr::Or(items) => {
412 for item in items {
413 replace_group_set_expression(item, mappings);
414 }
415 }
416 ScalarExpr::Binary { lhs, rhs, .. } => {
417 replace_group_set_expression(lhs, mappings);
418 replace_group_set_expression(rhs, mappings);
419 }
420 ScalarExpr::Not(inner)
421 | ScalarExpr::UnaryMinus(inner)
422 | ScalarExpr::IsNull { expr: inner, .. }
423 | ScalarExpr::Cast { expr: inner, .. } => {
424 replace_group_set_expression(inner, mappings);
425 }
426 ScalarExpr::Between { expr, low, high } => {
427 replace_group_set_expression(expr, mappings);
428 replace_group_set_expression(low, mappings);
429 replace_group_set_expression(high, mappings);
430 }
431 ScalarExpr::InList { expr, list, .. } => {
432 replace_group_set_expression(expr, mappings);
433 for item in list {
434 replace_group_set_expression(item, mappings);
435 }
436 }
437 ScalarExpr::WindowCall { args, spec, .. } => {
438 for argument in args {
439 replace_group_set_expression(argument, mappings);
440 }
441 for item in &mut spec.partition_by {
442 replace_group_set_expression(item, mappings);
443 }
444 for order in &mut spec.order_by {
445 replace_group_set_expression(&mut order.expr, mappings);
446 }
447 if let Some(frame) = &mut spec.frame {
448 replace_group_set_frame_bound(&mut frame.start, mappings);
449 replace_group_set_frame_bound(&mut frame.end, mappings);
450 }
451 }
452 ScalarExpr::Case {
453 base,
454 when,
455 else_branch,
456 } => {
457 if let Some(base) = base {
458 replace_group_set_expression(base, mappings);
459 }
460 for (condition, result) in when {
461 replace_group_set_expression(condition, mappings);
462 replace_group_set_expression(result, mappings);
463 }
464 if let Some(branch) = else_branch {
465 replace_group_set_expression(branch, mappings);
466 }
467 }
468 ScalarExpr::InSubquery { expr, .. } => {
469 replace_group_set_expression(expr, mappings);
470 }
471 ScalarExpr::Default
472 | ScalarExpr::Star
473 | ScalarExpr::QualifiedStar(_)
474 | ScalarExpr::Column(_)
475 | ScalarExpr::Position(_)
476 | ScalarExpr::InternalColumn(_)
477 | ScalarExpr::QualifiedColumn { .. }
478 | ScalarExpr::Literal(_)
479 | ScalarExpr::TypedLiteral { .. }
480 | ScalarExpr::Param(_)
481 | ScalarExpr::ScalarSubquery(_)
482 | ScalarExpr::Exists { .. } => {}
483 }
484}
485
486fn replace_group_set_frame_bound(
487 bound: &mut ScalarFrameBound,
488 mappings: &[(ScalarExpr, crate::ast::InternalColumnRef)],
489) {
490 match bound {
491 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
492 replace_group_set_expression(expression, mappings);
493 }
494 ScalarFrameBound::UnboundedPreceding
495 | ScalarFrameBound::UnboundedFollowing
496 | ScalarFrameBound::CurrentRow => {}
497 }
498}
499
500pub fn prepare_group_set_projection(
501 engine: &dyn SetFunctionCatalog,
502 resolver: &dyn FunctionTypeResolver,
503 statement: &QueryBlockPlan,
504 schema: &RowSchema,
505 params: &[SQLParam],
506) -> Result<Option<GroupSetProjectionPlan>, SQLError> {
507 let mut groups = Vec::new();
508 for expression in statement
509 .group_by
510 .iter()
511 .chain(statement.grouping_sets.iter().flatten())
512 {
513 if expression_may_return_set(engine, resolver, expression, schema, params)?
514 && !groups
515 .iter()
516 .any(|existing| exprs_match(existing, expression))
517 {
518 groups.push(expression.clone());
519 }
520 }
521 if groups.is_empty() {
522 return Ok(None);
523 }
524
525 let relation = crate::ast::InternalRelationId::allocate();
526 let mappings = groups
527 .iter()
528 .enumerate()
529 .map(|(index, expression)| (expression.clone(), relation.column(index)))
530 .collect::<Vec<_>>();
531 let projections = mappings
532 .iter()
533 .map(|(expression, column)| (ProjectionTarget::Internal(*column), expression.clone()))
534 .collect();
535 let mut rewritten = statement.clone();
536 let projection_labels = projection_columns(&rewritten.projections);
537 for expression in &mut rewritten.group_by {
538 replace_group_set_expression(expression, &mappings);
539 }
540 for set in &mut rewritten.grouping_sets {
541 for expression in set {
542 replace_group_set_expression(expression, &mappings);
543 }
544 }
545 for (projection, label) in rewritten.projections.iter_mut().zip(projection_labels) {
546 if projection.alias.is_none() {
547 projection.alias = Some(label);
548 }
549 replace_group_set_expression(&mut projection.expr, &mappings);
550 }
551 if let Some(having) = &mut rewritten.having {
552 replace_group_set_expression(having, &mappings);
553 }
554 for order in &mut rewritten.order_by {
555 replace_group_set_expression(&mut order.expr, &mappings);
556 }
557 for expression in &mut rewritten.distinct_on {
558 replace_group_set_expression(expression, &mappings);
559 }
560 Ok(Some(GroupSetProjectionPlan {
561 statement: rewritten,
562 projections,
563 }))
564}
565
566fn capture_aggregate_dependency(
567 expression: &ScalarExpr,
568 dependencies: &mut Vec<ProjectionPlan>,
569) -> ScalarExpr {
570 let position = dependencies.len();
571 dependencies.push(ProjectionPlan {
572 expr: expression.clone(),
573 alias: None,
574 });
575 ScalarExpr::Position(position)
576}
577
578#[expect(
579 clippy::too_many_lines,
580 reason = "preserves SELECT schema and row identity"
581)]
582fn rewrite_aggregate_dependencies(
583 engine: &dyn SetFunctionCatalog,
584 group_by: &[ScalarExpr],
585 expression: &ScalarExpr,
586 dependencies: &mut Vec<ProjectionPlan>,
587) -> ScalarExpr {
588 if is_aggregate(engine, expression)
589 || group_by.iter().any(|group| exprs_match(expression, group))
590 {
591 return capture_aggregate_dependency(expression, dependencies);
592 }
593 match expression {
594 ScalarExpr::Column(_)
595 | ScalarExpr::Position(_)
596 | ScalarExpr::InternalColumn(_)
597 | ScalarExpr::QualifiedColumn { .. } => {
598 capture_aggregate_dependency(expression, dependencies)
599 }
600 ScalarExpr::Func {
601 name,
602 binding,
603 args,
604 distinct,
605 order_by,
606 filter,
607 } => ScalarExpr::Func {
608 name: name.clone(),
609 binding: binding.clone(),
610 args: args
611 .iter()
612 .map(|argument| {
613 rewrite_aggregate_dependencies(engine, group_by, argument, dependencies)
614 })
615 .collect(),
616 distinct: *distinct,
617 order_by: order_by
618 .iter()
619 .map(|order| {
620 let mut order = order.clone();
621 order.expr =
622 rewrite_aggregate_dependencies(engine, group_by, &order.expr, dependencies);
623 order
624 })
625 .collect(),
626 filter: filter.as_deref().map(|filter| {
627 Box::new(rewrite_aggregate_dependencies(
628 engine,
629 group_by,
630 filter,
631 dependencies,
632 ))
633 }),
634 },
635 ScalarExpr::Array(items) => ScalarExpr::Array(
636 items
637 .iter()
638 .map(|item| rewrite_aggregate_dependencies(engine, group_by, item, dependencies))
639 .collect(),
640 ),
641 ScalarExpr::Row(items) => ScalarExpr::Row(
642 items
643 .iter()
644 .map(|item| rewrite_aggregate_dependencies(engine, group_by, item, dependencies))
645 .collect(),
646 ),
647 ScalarExpr::Binary { op, lhs, rhs } => ScalarExpr::Binary {
648 op: *op,
649 lhs: Box::new(rewrite_aggregate_dependencies(
650 engine,
651 group_by,
652 lhs,
653 dependencies,
654 )),
655 rhs: Box::new(rewrite_aggregate_dependencies(
656 engine,
657 group_by,
658 rhs,
659 dependencies,
660 )),
661 },
662 ScalarExpr::Not(inner) => ScalarExpr::Not(Box::new(rewrite_aggregate_dependencies(
663 engine,
664 group_by,
665 inner,
666 dependencies,
667 ))),
668 ScalarExpr::UnaryMinus(inner) => ScalarExpr::UnaryMinus(Box::new(
669 rewrite_aggregate_dependencies(engine, group_by, inner, dependencies),
670 )),
671 ScalarExpr::And(items) => ScalarExpr::And(
672 items
673 .iter()
674 .map(|item| rewrite_aggregate_dependencies(engine, group_by, item, dependencies))
675 .collect(),
676 ),
677 ScalarExpr::Or(items) => ScalarExpr::Or(
678 items
679 .iter()
680 .map(|item| rewrite_aggregate_dependencies(engine, group_by, item, dependencies))
681 .collect(),
682 ),
683 ScalarExpr::IsNull { expr, negated } => ScalarExpr::IsNull {
684 expr: Box::new(rewrite_aggregate_dependencies(
685 engine,
686 group_by,
687 expr,
688 dependencies,
689 )),
690 negated: *negated,
691 },
692 ScalarExpr::Between { expr, low, high } => ScalarExpr::Between {
693 expr: Box::new(rewrite_aggregate_dependencies(
694 engine,
695 group_by,
696 expr,
697 dependencies,
698 )),
699 low: Box::new(rewrite_aggregate_dependencies(
700 engine,
701 group_by,
702 low,
703 dependencies,
704 )),
705 high: Box::new(rewrite_aggregate_dependencies(
706 engine,
707 group_by,
708 high,
709 dependencies,
710 )),
711 },
712 ScalarExpr::InList {
713 expr,
714 list,
715 negated,
716 } => ScalarExpr::InList {
717 expr: Box::new(rewrite_aggregate_dependencies(
718 engine,
719 group_by,
720 expr,
721 dependencies,
722 )),
723 list: list
724 .iter()
725 .map(|item| rewrite_aggregate_dependencies(engine, group_by, item, dependencies))
726 .collect(),
727 negated: *negated,
728 },
729 ScalarExpr::WindowCall { name, args, spec } => {
730 let mut spec = spec.clone();
731 spec.partition_by = spec
732 .partition_by
733 .iter()
734 .map(|item| rewrite_aggregate_dependencies(engine, group_by, item, dependencies))
735 .collect();
736 for order in &mut spec.order_by {
737 order.expr =
738 rewrite_aggregate_dependencies(engine, group_by, &order.expr, dependencies);
739 }
740 if let Some(frame) = &mut spec.frame {
741 rewrite_aggregate_frame_bound(engine, group_by, &mut frame.start, dependencies);
742 rewrite_aggregate_frame_bound(engine, group_by, &mut frame.end, dependencies);
743 }
744 ScalarExpr::WindowCall {
745 name: name.clone(),
746 args: args
747 .iter()
748 .map(|argument| {
749 rewrite_aggregate_dependencies(engine, group_by, argument, dependencies)
750 })
751 .collect(),
752 spec,
753 }
754 }
755 ScalarExpr::Case {
756 base,
757 when,
758 else_branch,
759 } => ScalarExpr::Case {
760 base: base.as_deref().map(|base| {
761 Box::new(rewrite_aggregate_dependencies(
762 engine,
763 group_by,
764 base,
765 dependencies,
766 ))
767 }),
768 when: when
769 .iter()
770 .map(|(condition, result)| {
771 (
772 rewrite_aggregate_dependencies(engine, group_by, condition, dependencies),
773 rewrite_aggregate_dependencies(engine, group_by, result, dependencies),
774 )
775 })
776 .collect(),
777 else_branch: else_branch.as_deref().map(|branch| {
778 Box::new(rewrite_aggregate_dependencies(
779 engine,
780 group_by,
781 branch,
782 dependencies,
783 ))
784 }),
785 },
786 ScalarExpr::Cast { expr, ty } => ScalarExpr::Cast {
787 expr: Box::new(rewrite_aggregate_dependencies(
788 engine,
789 group_by,
790 expr,
791 dependencies,
792 )),
793 ty: ty.clone(),
794 },
795 ScalarExpr::InSubquery {
796 expr,
797 subquery,
798 negated,
799 } => ScalarExpr::InSubquery {
800 expr: Box::new(rewrite_aggregate_dependencies(
801 engine,
802 group_by,
803 expr,
804 dependencies,
805 )),
806 subquery: *subquery,
807 negated: *negated,
808 },
809 ScalarExpr::Default
810 | ScalarExpr::Star
811 | ScalarExpr::QualifiedStar(_)
812 | ScalarExpr::Literal(_)
813 | ScalarExpr::TypedLiteral { .. }
814 | ScalarExpr::Param(_)
815 | ScalarExpr::ScalarSubquery(_)
816 | ScalarExpr::Exists { .. } => expression.clone(),
817 }
818}
819
820fn rewrite_aggregate_frame_bound(
821 engine: &dyn SetFunctionCatalog,
822 group_by: &[ScalarExpr],
823 bound: &mut ScalarFrameBound,
824 dependencies: &mut Vec<ProjectionPlan>,
825) {
826 match bound {
827 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
828 **expression =
829 rewrite_aggregate_dependencies(engine, group_by, expression, dependencies);
830 }
831 ScalarFrameBound::UnboundedPreceding
832 | ScalarFrameBound::UnboundedFollowing
833 | ScalarFrameBound::CurrentRow => {}
834 }
835}
836
837pub fn prepare_aggregate_output_projection(
838 engine: &dyn SetFunctionCatalog,
839 statement: &QueryBlockPlan,
840 internal_targets: &[(usize, crate::ast::InternalColumnRef)],
841) -> AggregateOutputProjectionPlan {
842 let labels = projection_columns(&statement.projections);
843 let mut dependencies = Vec::new();
844 let projections = statement
845 .projections
846 .iter()
847 .enumerate()
848 .zip(labels)
849 .map(|((position, projection), label)| {
850 let target = internal_targets
851 .iter()
852 .find(|(target_position, _)| *target_position == position)
853 .map_or_else(
854 || ProjectionTarget::Column(label),
855 |(_, column)| ProjectionTarget::Internal(*column),
856 );
857 (
858 target,
859 rewrite_aggregate_dependencies(
860 engine,
861 &statement.group_by,
862 &projection.expr,
863 &mut dependencies,
864 ),
865 )
866 })
867 .collect();
868 if dependencies.is_empty() {
869 dependencies.push(ProjectionPlan {
870 expr: ScalarExpr::Literal(Value::Int(1)),
871 alias: None,
872 });
873 }
874 let mut aggregate_statement = statement.clone();
875 aggregate_statement.projections = dependencies;
876 AggregateOutputProjectionPlan {
877 statement: aggregate_statement,
878 projections,
879 }
880}