Skip to main content

icydb_core/db/query/builder/
aggregate.rs

1//! Module: query::builder::aggregate
2//! Responsibility: composable grouped/global aggregate expression builders.
3//! Does not own: aggregate validation policy or executor fold semantics.
4//! Boundary: fluent aggregate intent construction lowered into grouped specs.
5
6use crate::db::query::plan::AggregateKind;
7
8///
9/// AggregateExpr
10///
11/// Composable aggregate expression used by query/fluent aggregate entrypoints.
12/// This builder only carries declarative shape (`kind`, `target_field`,
13/// `distinct`) and does not perform semantic validation.
14///
15
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct AggregateExpr {
18    kind: AggregateKind,
19    target_field: Option<String>,
20    distinct: bool,
21}
22
23impl AggregateExpr {
24    /// Construct one aggregate expression from explicit shape components.
25    const fn new(kind: AggregateKind, target_field: Option<String>) -> Self {
26        Self {
27            kind,
28            target_field,
29            distinct: false,
30        }
31    }
32
33    /// Enable DISTINCT modifier for this aggregate expression.
34    #[must_use]
35    pub const fn distinct(mut self) -> Self {
36        self.distinct = true;
37        self
38    }
39
40    /// Borrow aggregate kind.
41    #[must_use]
42    pub(crate) const fn kind(&self) -> AggregateKind {
43        self.kind
44    }
45
46    /// Borrow optional target field.
47    #[must_use]
48    pub(crate) fn target_field(&self) -> Option<&str> {
49        self.target_field.as_deref()
50    }
51
52    /// Return true when DISTINCT is enabled.
53    #[must_use]
54    pub(crate) const fn is_distinct(&self) -> bool {
55        self.distinct
56    }
57
58    /// Build one aggregate expression directly from planner semantic parts.
59    pub(in crate::db::query) const fn from_semantic_parts(
60        kind: AggregateKind,
61        target_field: Option<String>,
62        distinct: bool,
63    ) -> Self {
64        Self {
65            kind,
66            target_field,
67            distinct,
68        }
69    }
70
71    /// Build one non-field-target terminal aggregate expression from one kind.
72    #[must_use]
73    pub(in crate::db) fn terminal_for_kind(kind: AggregateKind) -> Self {
74        match kind {
75            AggregateKind::Count => count(),
76            AggregateKind::Exists => exists(),
77            AggregateKind::Min => min(),
78            AggregateKind::Max => max(),
79            AggregateKind::First => first(),
80            AggregateKind::Last => last(),
81            AggregateKind::Sum | AggregateKind::Avg => unreachable!(
82                "AggregateExpr::terminal_for_kind does not support SUM/AVG field-target kinds"
83            ),
84        }
85    }
86
87    /// Build one field-target extrema aggregate expression from one kind.
88    #[must_use]
89    pub(in crate::db) fn field_target_extrema_for_kind(
90        kind: AggregateKind,
91        field: impl AsRef<str>,
92    ) -> Self {
93        match kind {
94            AggregateKind::Min => min_by(field),
95            AggregateKind::Max => max_by(field),
96            _ => unreachable!("AggregateExpr::field_target_extrema_for_kind requires MIN/MAX kind"),
97        }
98    }
99}
100
101/// Build `count(*)`.
102#[must_use]
103pub const fn count() -> AggregateExpr {
104    AggregateExpr::new(AggregateKind::Count, None)
105}
106
107/// Build `count(field)`.
108#[must_use]
109pub fn count_by(field: impl AsRef<str>) -> AggregateExpr {
110    AggregateExpr::new(AggregateKind::Count, Some(field.as_ref().to_string()))
111}
112
113/// Build `sum(field)`.
114#[must_use]
115pub fn sum(field: impl AsRef<str>) -> AggregateExpr {
116    AggregateExpr::new(AggregateKind::Sum, Some(field.as_ref().to_string()))
117}
118
119/// Build `avg(field)`.
120#[must_use]
121pub fn avg(field: impl AsRef<str>) -> AggregateExpr {
122    AggregateExpr::new(AggregateKind::Avg, Some(field.as_ref().to_string()))
123}
124
125/// Build `exists`.
126#[must_use]
127pub const fn exists() -> AggregateExpr {
128    AggregateExpr::new(AggregateKind::Exists, None)
129}
130
131/// Build `first`.
132#[must_use]
133pub const fn first() -> AggregateExpr {
134    AggregateExpr::new(AggregateKind::First, None)
135}
136
137/// Build `last`.
138#[must_use]
139pub const fn last() -> AggregateExpr {
140    AggregateExpr::new(AggregateKind::Last, None)
141}
142
143/// Build `min`.
144#[must_use]
145pub const fn min() -> AggregateExpr {
146    AggregateExpr::new(AggregateKind::Min, None)
147}
148
149/// Build `min(field)`.
150#[must_use]
151pub fn min_by(field: impl AsRef<str>) -> AggregateExpr {
152    AggregateExpr::new(AggregateKind::Min, Some(field.as_ref().to_string()))
153}
154
155/// Build `max`.
156#[must_use]
157pub const fn max() -> AggregateExpr {
158    AggregateExpr::new(AggregateKind::Max, None)
159}
160
161/// Build `max(field)`.
162#[must_use]
163pub fn max_by(field: impl AsRef<str>) -> AggregateExpr {
164    AggregateExpr::new(AggregateKind::Max, Some(field.as_ref().to_string()))
165}