Plan an aggregate query (GROUP BY + aggregate functions).
(
select: &ast::Select,
table: &ResolvedTable,
filters: &[Filter],
_scope: &crate::resolver::columns::TableScope,
functions: &FunctionRegistry,
temporal: &TemporalScope,
)
| 16 | |
| 17 | /// Plan an aggregate query (GROUP BY + aggregate functions). |
| 18 | pub fn plan_aggregate( |
| 19 | select: &ast::Select, |
| 20 | table: &ResolvedTable, |
| 21 | filters: &[Filter], |
| 22 | _scope: &crate::resolver::columns::TableScope, |
| 23 | functions: &FunctionRegistry, |
| 24 | temporal: &TemporalScope, |
| 25 | ) -> Result<SqlPlan> { |
| 26 | // Detect ROLLUP / CUBE / GROUPING SETS before falling through to plain convert. |
| 27 | let grouping_expansion = expand_group_by(&select.group_by)?; |
| 28 | |
| 29 | let (group_by_exprs, grouping_sets) = if let Some(exp) = grouping_expansion { |
| 30 | (exp.canonical_keys, Some(exp.grouping_sets)) |
| 31 | } else { |
| 32 | (convert_group_by(&select.group_by)?, None) |
| 33 | }; |
| 34 | |
| 35 | let mut aggregates = extract_aggregates_from_projection(&select.projection, functions)?; |
| 36 | let having = match &select.having { |
| 37 | Some(expr) => super::select::convert_where_to_filters(expr)?, |
| 38 | None => Vec::new(), |
| 39 | }; |
| 40 | |
| 41 | // When grouping sets are present, detect GROUPING(col) in the projection and |
| 42 | // synthesize AggregateExpr entries so the executor can compute them per-set. |
| 43 | if grouping_sets.is_some() { |
| 44 | let grouping_aggs = extract_grouping_calls(&select.projection, &group_by_exprs)?; |
| 45 | aggregates.extend(grouping_aggs); |
| 46 | } |
| 47 | |
| 48 | // Extract timeseries-specific params (bucket interval, group columns) if applicable. |
| 49 | let (bucket_interval_ms, group_columns) = |
| 50 | extract_timeseries_params(&select.group_by, &select.projection, functions)?; |
| 51 | |
| 52 | let rules = engine_rules::resolve_engine_rules(table.info.engine); |
| 53 | let base_plan = rules.plan_aggregate(AggregateParams { |
| 54 | collection: table.name.clone(), |
| 55 | alias: table.alias.clone(), |
| 56 | filters: filters.to_vec(), |
| 57 | group_by: group_by_exprs.clone(), |
| 58 | aggregates: aggregates.clone(), |
| 59 | having: having.clone(), |
| 60 | limit: 10000, |
| 61 | bucket_interval_ms, |
| 62 | group_columns, |
| 63 | has_auto_tier: table.info.has_auto_tier, |
| 64 | bitemporal: table.info.bitemporal, |
| 65 | temporal: *temporal, |
| 66 | })?; |
| 67 | |
| 68 | // Wrap the plan to attach grouping sets if present. |
| 69 | if let Some(sets) = grouping_sets { |
| 70 | return Ok(attach_grouping_sets( |
| 71 | base_plan, |
| 72 | group_by_exprs, |
| 73 | aggregates, |
| 74 | having, |
| 75 | sets, |
no test coverage detected