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