Skip to main content

icydb_core/db/query/builder/aggregate/
expr.rs

1use crate::db::query::plan::{
2    AggregateKind,
3    expr::{Expr, FieldId, canonicalize_aggregate_input_expr},
4};
5
6///
7/// AggregateExpr
8///
9/// Composable aggregate expression used by query/fluent aggregate entrypoints.
10/// This builder only carries declarative shape (`kind`, aggregate input
11/// expression, optional filter expression, `distinct`) and does not perform
12/// semantic validation.
13///
14
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub struct AggregateExpr {
17    kind: AggregateKind,
18    input_expr: Option<Box<Expr>>,
19    filter_expr: Option<Box<Expr>>,
20    distinct: bool,
21}
22
23impl AggregateExpr {
24    /// Construct one terminal aggregate expression with no input expression.
25    const fn terminal(kind: AggregateKind) -> Self {
26        Self {
27            kind,
28            input_expr: None,
29            filter_expr: None,
30            distinct: false,
31        }
32    }
33
34    /// Construct one aggregate expression over one canonical field leaf.
35    fn field_target(kind: AggregateKind, field: impl Into<String>) -> Self {
36        Self {
37            kind,
38            input_expr: Some(Box::new(Expr::Field(FieldId::new(field.into())))),
39            filter_expr: None,
40            distinct: false,
41        }
42    }
43
44    /// Construct one aggregate expression from one planner-owned input expression.
45    pub(in crate::db) fn from_expression_input(kind: AggregateKind, input_expr: Expr) -> Self {
46        Self {
47            kind,
48            input_expr: Some(Box::new(canonicalize_aggregate_input_expr(
49                kind, input_expr,
50            ))),
51            filter_expr: None,
52            distinct: false,
53        }
54    }
55
56    /// Attach one planner-owned pre-aggregate filter expression to this aggregate.
57    #[must_use]
58    pub(in crate::db) fn with_filter_expr(mut self, filter_expr: Expr) -> Self {
59        self.filter_expr = Some(Box::new(filter_expr));
60        self
61    }
62
63    /// Enable DISTINCT modifier for this aggregate expression.
64    #[must_use]
65    pub const fn distinct(mut self) -> Self {
66        self.distinct = true;
67        self
68    }
69
70    /// Borrow aggregate kind.
71    #[must_use]
72    pub(in crate::db) const fn kind(&self) -> AggregateKind {
73        self.kind
74    }
75
76    /// Borrow the aggregate input expression, if any.
77    #[must_use]
78    pub(in crate::db) fn input_expr(&self) -> Option<&Expr> {
79        self.input_expr.as_deref()
80    }
81
82    /// Borrow the aggregate filter expression, if any.
83    #[must_use]
84    pub(in crate::db) fn filter_expr(&self) -> Option<&Expr> {
85        self.filter_expr.as_deref()
86    }
87
88    /// Borrow the optional target field when this aggregate input stays a plain field leaf.
89    #[must_use]
90    pub(in crate::db) fn target_field(&self) -> Option<&str> {
91        match self.input_expr() {
92            Some(Expr::Field(field)) => Some(field.as_str()),
93            _ => None,
94        }
95    }
96
97    /// Return true when DISTINCT is enabled.
98    #[must_use]
99    pub(in crate::db) const fn is_distinct(&self) -> bool {
100        self.distinct
101    }
102
103    /// Build one aggregate expression directly from planner semantic parts.
104    pub(in crate::db::query) fn from_semantic_parts(
105        kind: AggregateKind,
106        target_field: Option<String>,
107        distinct: bool,
108    ) -> Self {
109        Self {
110            kind,
111            input_expr: target_field.map(|field| Box::new(Expr::Field(FieldId::new(field)))),
112            filter_expr: None,
113            distinct,
114        }
115    }
116
117    /// Build one non-field-target terminal aggregate expression from one kind.
118    #[cfg(test)]
119    #[must_use]
120    pub(in crate::db) fn terminal_for_kind(kind: AggregateKind) -> Self {
121        match kind {
122            AggregateKind::Count => count(),
123            AggregateKind::Exists => exists(),
124            AggregateKind::Min => min(),
125            AggregateKind::Max => max(),
126            AggregateKind::First => first(),
127            AggregateKind::Last => last(),
128            AggregateKind::Sum | AggregateKind::Avg => unreachable!(
129                "AggregateExpr::terminal_for_kind does not support SUM/AVG field-target kinds"
130            ),
131        }
132    }
133}
134
135/// Build `count(*)`.
136#[must_use]
137pub const fn count() -> AggregateExpr {
138    AggregateExpr::terminal(AggregateKind::Count)
139}
140
141/// Build `count(field)`.
142#[must_use]
143pub fn count_by(field: impl AsRef<str>) -> AggregateExpr {
144    AggregateExpr::field_target(AggregateKind::Count, field.as_ref().to_string())
145}
146
147/// Build `sum(field)`.
148#[must_use]
149pub fn sum(field: impl AsRef<str>) -> AggregateExpr {
150    AggregateExpr::field_target(AggregateKind::Sum, field.as_ref().to_string())
151}
152
153/// Build `avg(field)`.
154#[must_use]
155pub fn avg(field: impl AsRef<str>) -> AggregateExpr {
156    AggregateExpr::field_target(AggregateKind::Avg, field.as_ref().to_string())
157}
158
159/// Build `exists`.
160#[must_use]
161pub const fn exists() -> AggregateExpr {
162    AggregateExpr::terminal(AggregateKind::Exists)
163}
164
165/// Build `first`.
166#[must_use]
167pub const fn first() -> AggregateExpr {
168    AggregateExpr::terminal(AggregateKind::First)
169}
170
171/// Build `last`.
172#[must_use]
173pub const fn last() -> AggregateExpr {
174    AggregateExpr::terminal(AggregateKind::Last)
175}
176
177/// Build `min`.
178#[must_use]
179pub const fn min() -> AggregateExpr {
180    AggregateExpr::terminal(AggregateKind::Min)
181}
182
183/// Build `min(field)`.
184#[must_use]
185pub fn min_by(field: impl AsRef<str>) -> AggregateExpr {
186    AggregateExpr::field_target(AggregateKind::Min, field.as_ref().to_string())
187}
188
189/// Build `max`.
190#[must_use]
191pub const fn max() -> AggregateExpr {
192    AggregateExpr::terminal(AggregateKind::Max)
193}
194
195/// Build `max(field)`.
196#[must_use]
197pub fn max_by(field: impl AsRef<str>) -> AggregateExpr {
198    AggregateExpr::field_target(AggregateKind::Max, field.as_ref().to_string())
199}