Skip to main content

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

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