Skip to main content

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

1use crate::{
2    db::query::{
3        construction::ConstructionBudget,
4        plan::{AggregateKind, AggregateShape, expr::Expr},
5    },
6    error::InternalError,
7};
8
9///
10/// AggregateExpr
11///
12/// Composable aggregate expression used by query/fluent aggregate entrypoints.
13/// This builder only carries declarative shape (`kind`, aggregate input
14/// expression, optional filter expression, `distinct`) and does not perform
15/// semantic validation.
16///
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct AggregateExpr {
20    shape: AggregateShape,
21}
22
23impl AggregateExpr {
24    /// Copy this admitted declaration against the current preparation request.
25    pub(in crate::db) fn copy_for_preparation(
26        &self,
27        work: &dyn ConstructionBudget,
28    ) -> Result<Self, InternalError> {
29        Ok(Self::from_shape(self.shape.copy_for_preparation(work)?))
30    }
31
32    /// Detach recursive children for iterative owned-request cleanup.
33    pub(in crate::db) const fn take_expressions(&mut self) -> [Option<Box<Expr>>; 2] {
34        self.shape.take_expressions()
35    }
36
37    /// Construct one terminal aggregate expression with no input expression.
38    const fn terminal(kind: AggregateKind) -> Self {
39        Self {
40            shape: AggregateShape::terminal(kind),
41        }
42    }
43
44    /// Construct one aggregate expression over one canonical field leaf.
45    fn field_target(kind: AggregateKind, field: impl Into<String>) -> Self {
46        Self {
47            shape: AggregateShape::field_target(kind, field.into()),
48        }
49    }
50
51    /// Construct one aggregate expression from one planner-owned input expression.
52    pub(in crate::db) fn from_expression_input(kind: AggregateKind, input_expr: Expr) -> Self {
53        Self {
54            shape: AggregateShape::from_expression_input(kind, input_expr),
55        }
56    }
57
58    /// Build one aggregate expression from the canonical raw aggregate shape.
59    #[must_use]
60    pub(in crate::db) const fn from_shape(shape: AggregateShape) -> Self {
61        Self { shape }
62    }
63
64    /// Transfer the authored shape without copying or re-normalizing operands.
65    #[must_use]
66    pub(in crate::db) fn into_shape(self) -> AggregateShape {
67        self.shape
68    }
69
70    /// Attach one planner-owned pre-aggregate filter expression to this aggregate.
71    #[must_use]
72    pub(in crate::db) fn with_filter_expr(mut self, filter_expr: Expr) -> Self {
73        self.shape = self.shape.with_filter_expr(filter_expr);
74        self
75    }
76
77    /// Enable DISTINCT modifier for this aggregate expression.
78    #[must_use]
79    pub const fn distinct(mut self) -> Self {
80        self.shape.set_raw_distinct(true);
81        self
82    }
83
84    /// Borrow aggregate kind.
85    #[must_use]
86    pub(in crate::db) const fn kind(&self) -> AggregateKind {
87        self.shape.kind()
88    }
89
90    /// Borrow the aggregate input expression, if any.
91    #[must_use]
92    pub(in crate::db) fn input_expr(&self) -> Option<&Expr> {
93        self.shape.input_expr()
94    }
95
96    /// Borrow the aggregate filter expression, if any.
97    #[must_use]
98    pub(in crate::db) fn filter_expr(&self) -> Option<&Expr> {
99        self.shape.filter_expr()
100    }
101
102    /// Borrow the optional target field when this aggregate input stays a plain field leaf.
103    #[must_use]
104    pub(in crate::db) fn target_field(&self) -> Option<&str> {
105        match self.input_expr() {
106            Some(Expr::Field(field)) => Some(field.as_str()),
107            _ => None,
108        }
109    }
110
111    /// Return true when DISTINCT is enabled.
112    #[must_use]
113    pub(in crate::db) const fn is_distinct(&self) -> bool {
114        self.shape.raw_distinct()
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}
200
201// Exhaustive cache-retention coverage; new owned fields require accounting.
202crate::retained::retained_fields!(AggregateExpr {
203Self{shape} => [shape],
204});