Skip to main content

nodedb_sql/planner/
aggregate_clauses.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! The GROUP BY, aggregate, HAVING and output-order clauses of one SELECT,
4//! planned once for every aggregate source: a single collection or a join.
5
6use sqlparser::ast;
7
8use crate::error::Result;
9use crate::functions::registry::FunctionRegistry;
10use crate::planner::agg_bind::{BindName, bind_aggregate_calls};
11use crate::planner::group_by::{convert_group_by_with_projection, group_by_output_aliases};
12use crate::planner::grouping_sets::expand_group_by;
13use crate::resolver::columns::TableScope;
14use crate::types::{AggOutputSlot, AggregateExpr, ColumnInfo, Filter, SqlExpr};
15
16/// The planned aggregate clauses of a SELECT.
17pub struct AggregateClauses {
18    /// GROUP BY keys. With grouping sets, the canonical deduplicated keys.
19    pub group_by: Vec<SqlExpr>,
20    /// ROLLUP / CUBE / GROUPING SETS expansion, as indices into `group_by`.
21    pub grouping_sets: Option<Vec<Vec<usize>>>,
22    /// SELECT-list aggregates, then any aggregate only HAVING names, then
23    /// the `GROUPING(col)` pseudo-aggregates.
24    pub aggregates: Vec<AggregateExpr>,
25    /// HAVING predicates bound to the aggregates' output keys.
26    pub having: Vec<Filter>,
27    /// SELECT-list output aliases of the GROUP BY keys.
28    pub group_by_aliases: Vec<Option<String>>,
29    /// SELECT-list interleaving of keys and aggregates.
30    pub output_order: Vec<AggOutputSlot>,
31    /// Whether HAVING or ORDER BY computes an aggregate the SELECT list does
32    /// not show.
33    pub hidden_aggregates: bool,
34}
35
36/// Plan the aggregate clauses of `select` against `scope`.
37///
38/// `table_columns` holds the declared columns of a single-collection
39/// source, so a GROUP BY name that is an input column wins over a SELECT
40/// alias. A join passes an empty slice. `order_by` is the enclosing query's
41/// ORDER BY, read only to learn whether it names an aggregate of its own.
42pub fn plan_aggregate_clauses(
43    select: &ast::Select,
44    order_by: Option<&ast::OrderBy>,
45    table_columns: &[ColumnInfo],
46    scope: &TableScope,
47    functions: &FunctionRegistry,
48) -> Result<AggregateClauses> {
49    let (group_by, grouping_sets) = match expand_group_by(&select.group_by, scope)? {
50        Some(expansion) => (expansion.canonical_keys, Some(expansion.grouping_sets)),
51        None => (
52            convert_group_by_with_projection(
53                &select.group_by,
54                &select.projection,
55                table_columns,
56                scope,
57            )?,
58            None,
59        ),
60    };
61
62    let mut aggregates =
63        super::aggregate::extract_aggregates_from_projection(&select.projection, functions, scope)?;
64    let projected = aggregates.len();
65    // HAVING binds to the aggregates' computed output keys. An aggregate it
66    // alone introduces is appended so it is computed.
67    let having = match &select.having {
68        Some(expr) => {
69            super::having::plan_having(expr, &select.projection, &mut aggregates, functions, scope)?
70        }
71        None => Vec::new(),
72    };
73    let hidden_aggregates = aggregates.len() > projected
74        || sort_names_new_aggregate(order_by, &select.projection, &aggregates, functions, scope)?;
75
76    if grouping_sets.is_some() {
77        let grouping_aggs =
78            super::aggregate::extract_grouping_calls(&select.projection, &group_by, scope)?;
79        aggregates.extend(grouping_aggs);
80    }
81
82    let output_order = super::aggregate_order::compute_output_order(
83        &select.projection,
84        &group_by,
85        functions,
86        scope,
87    )?;
88    let group_by_aliases = group_by_output_aliases(&select.projection, &group_by);
89
90    Ok(AggregateClauses {
91        group_by,
92        grouping_sets,
93        aggregates,
94        having,
95        group_by_aliases,
96        output_order,
97        hidden_aggregates,
98    })
99}
100
101/// Whether ORDER BY names an aggregate that neither the SELECT list nor
102/// HAVING computes.
103fn sort_names_new_aggregate(
104    order_by: Option<&ast::OrderBy>,
105    projection: &[ast::SelectItem],
106    aggregates: &[AggregateExpr],
107    functions: &FunctionRegistry,
108    scope: &TableScope,
109) -> Result<bool> {
110    let Some(ast::OrderBy {
111        kind: ast::OrderByKind::Expressions(exprs),
112        ..
113    }) = order_by
114    else {
115        return Ok(false);
116    };
117    let mut extended = aggregates.to_vec();
118    for o in exprs {
119        bind_aggregate_calls(
120            &o.expr,
121            projection,
122            &mut extended,
123            functions,
124            BindName::Canonical,
125            scope,
126        )?;
127    }
128    Ok(extended.len() > aggregates.len())
129}