Skip to main content

alopex_sql/planner/
aggregate_expr.rs

1use crate::planner::typed_expr::{SortExpr, TypedExpr};
2use crate::planner::types::ResolvedType;
3
4/// Supported aggregate function types.
5#[derive(Debug, Clone, PartialEq)]
6pub enum AggregateFunction {
7    Count,
8    Sum,
9    Total,
10    Avg,
11    Min,
12    Max,
13    GroupConcat {
14        separator: Option<String>,
15    },
16    StringAgg {
17        separator: Option<String>,
18    },
19    /// Ordered-set aggregate `PERCENTILE_DISC(fraction) WITHIN GROUP
20    /// (ORDER BY ...)` (issue #148). `fraction` is validated by the planner to
21    /// be a literal in `[0, 1]`. Issue #154 (PERCENTILE_CONT / MODE) extends
22    /// this enum with sibling variants reusing the same ordered-input path.
23    PercentileDisc {
24        fraction: f64,
25    },
26    PercentileCont {
27        fraction: f64,
28    },
29    QuantileCont {
30        fraction: f64,
31    },
32    Variance {
33        sample: bool,
34    },
35    Stddev {
36        sample: bool,
37    },
38    Covariance {
39        sample: bool,
40    },
41    Corr,
42    Median,
43    Mode,
44    RegrCount,
45    RegrAvgX,
46    RegrAvgY,
47    RegrSxx,
48    RegrSyy,
49    RegrSxy,
50    RegrSlope,
51    RegrIntercept,
52    RegrR2,
53    AnyValue,
54    First,
55    Last,
56    ArgMin,
57    ArgMax,
58    BitAnd,
59    BitOr,
60    BitXor,
61    BoolAnd,
62    BoolOr,
63}
64
65/// Aggregate expression definition.
66#[derive(Debug, Clone)]
67pub struct AggregateExpr {
68    pub function: AggregateFunction,
69    pub arg: Option<TypedExpr>,
70    /// Additional inputs for two-argument aggregates. Existing accumulators
71    /// remain single-input through `arg`; only covariance, regression, and
72    /// arg-min/max consume this vector.
73    pub extra_args: Vec<TypedExpr>,
74    pub distinct: bool,
75    pub result_type: ResolvedType,
76    /// `FILTER (WHERE predicate)`: rows where the predicate is not TRUE are
77    /// skipped before the accumulator (and any DISTINCT set) sees them.
78    pub filter: Option<TypedExpr>,
79    /// Aggregate-local ordering. Non-empty only for order-sensitive
80    /// aggregates (GROUP_CONCAT / STRING_AGG / ordered-set aggregates); the
81    /// planner discards validated ORDER BY on order-insensitive aggregates
82    /// (D3 in docs/sql-aggregate-filter-within-group.md).
83    pub order_by: Vec<SortExpr>,
84}
85
86impl AggregateExpr {
87    pub fn count_star() -> Self {
88        Self {
89            function: AggregateFunction::Count,
90            arg: None,
91            extra_args: Vec::new(),
92            distinct: false,
93            result_type: ResolvedType::BigInt,
94            filter: None,
95            order_by: Vec::new(),
96        }
97    }
98
99    pub fn count(arg: TypedExpr, distinct: bool) -> Self {
100        Self {
101            function: AggregateFunction::Count,
102            arg: Some(arg),
103            extra_args: Vec::new(),
104            distinct,
105            result_type: ResolvedType::BigInt,
106            filter: None,
107            order_by: Vec::new(),
108        }
109    }
110
111    pub fn sum(arg: TypedExpr) -> Self {
112        let result_type = sum_result_type(&arg.resolved_type);
113        Self {
114            function: AggregateFunction::Sum,
115            arg: Some(arg),
116            extra_args: Vec::new(),
117            distinct: false,
118            result_type,
119            filter: None,
120            order_by: Vec::new(),
121        }
122    }
123
124    pub fn total(arg: TypedExpr) -> Self {
125        Self {
126            function: AggregateFunction::Total,
127            arg: Some(arg),
128            extra_args: Vec::new(),
129            distinct: false,
130            result_type: ResolvedType::Double,
131            filter: None,
132            order_by: Vec::new(),
133        }
134    }
135
136    pub fn avg(arg: TypedExpr) -> Self {
137        Self {
138            function: AggregateFunction::Avg,
139            arg: Some(arg),
140            extra_args: Vec::new(),
141            distinct: false,
142            result_type: ResolvedType::Double,
143            filter: None,
144            order_by: Vec::new(),
145        }
146    }
147
148    pub fn min(arg: TypedExpr) -> Self {
149        let result_type = arg.resolved_type.clone();
150        Self {
151            function: AggregateFunction::Min,
152            arg: Some(arg),
153            extra_args: Vec::new(),
154            distinct: false,
155            result_type,
156            filter: None,
157            order_by: Vec::new(),
158        }
159    }
160
161    pub fn max(arg: TypedExpr) -> Self {
162        let result_type = arg.resolved_type.clone();
163        Self {
164            function: AggregateFunction::Max,
165            arg: Some(arg),
166            extra_args: Vec::new(),
167            distinct: false,
168            result_type,
169            filter: None,
170            order_by: Vec::new(),
171        }
172    }
173}
174
175/// Return the SQL result type for `SUM` over a value of `input_type`.
176///
177/// Fixed-width integral inputs retain their integral type. All other numeric
178/// inputs accumulate and return DOUBLE, matching the historical floating-point
179/// behaviour and keeping `TOTAL`/`AVG` semantics distinct.
180/// `SUM` keeps integer inputs exact, but accumulates them in a wider type: a
181/// 32-bit accumulator overflows on ordinary data, so summing INTEGER yields
182/// BIGINT. PostgreSQL sums int4 into int8 for the same reason, and DuckDB
183/// widens further to hugeint.
184pub fn sum_result_type(input_type: &ResolvedType) -> ResolvedType {
185    match input_type {
186        ResolvedType::Integer | ResolvedType::BigInt => ResolvedType::BigInt,
187        _ => ResolvedType::Double,
188    }
189}