icydb_core/db/query/builder/
aggregate.rs1use crate::db::{direction::Direction, query::plan::AggregateKind};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct AggregateExpr {
18 kind: AggregateKind,
19 target_field: Option<String>,
20 distinct: bool,
21}
22
23impl AggregateExpr {
24 const fn new(kind: AggregateKind, target_field: Option<String>) -> Self {
26 Self {
27 kind,
28 target_field,
29 distinct: false,
30 }
31 }
32
33 #[must_use]
35 pub const fn distinct(mut self) -> Self {
36 self.distinct = true;
37 self
38 }
39
40 #[must_use]
42 pub(crate) const fn kind(&self) -> AggregateKind {
43 self.kind
44 }
45
46 #[must_use]
48 pub(crate) fn target_field(&self) -> Option<&str> {
49 self.target_field.as_deref()
50 }
51
52 #[must_use]
54 pub(crate) const fn is_distinct(&self) -> bool {
55 self.distinct
56 }
57
58 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 #[must_use]
73 pub(crate) const fn is_count_kind(kind: AggregateKind) -> bool {
74 matches!(kind, AggregateKind::Count)
75 }
76
77 #[must_use]
79 pub(crate) const fn is_sum_kind(kind: AggregateKind) -> bool {
80 matches!(kind, AggregateKind::Sum | AggregateKind::Avg)
81 }
82
83 #[must_use]
85 pub(crate) const fn supports_field_targets_kind(kind: AggregateKind) -> bool {
86 matches!(
87 kind,
88 AggregateKind::Min | AggregateKind::Max | AggregateKind::Sum | AggregateKind::Avg
89 )
90 }
91
92 #[must_use]
94 pub(crate) const fn is_extrema_kind(kind: AggregateKind) -> bool {
95 Self::supports_field_targets_kind(kind)
96 }
97
98 #[must_use]
100 pub(crate) const fn requires_decoded_id_kind(kind: AggregateKind) -> bool {
101 !matches!(
102 kind,
103 AggregateKind::Count | AggregateKind::Sum | AggregateKind::Avg | AggregateKind::Exists
104 )
105 }
106
107 #[must_use]
109 pub(crate) const fn supports_grouped_distinct_kind_v1(kind: AggregateKind) -> bool {
110 matches!(
111 kind,
112 AggregateKind::Count
113 | AggregateKind::Min
114 | AggregateKind::Max
115 | AggregateKind::Sum
116 | AggregateKind::Avg
117 )
118 }
119
120 #[must_use]
122 pub(crate) const fn supports_global_distinct_without_group_keys_kind(
123 kind: AggregateKind,
124 ) -> bool {
125 matches!(
126 kind,
127 AggregateKind::Count | AggregateKind::Sum | AggregateKind::Avg
128 )
129 }
130
131 #[must_use]
133 pub(crate) const fn extrema_direction_for_kind(kind: AggregateKind) -> Option<Direction> {
134 match kind {
135 AggregateKind::Min => Some(Direction::Asc),
136 AggregateKind::Max => Some(Direction::Desc),
137 AggregateKind::Count
138 | AggregateKind::Sum
139 | AggregateKind::Avg
140 | AggregateKind::Exists
141 | AggregateKind::First
142 | AggregateKind::Last => None,
143 }
144 }
145
146 #[must_use]
148 pub(crate) const fn materialized_fold_direction_for_kind(kind: AggregateKind) -> Direction {
149 match kind {
150 AggregateKind::Min => Direction::Desc,
151 AggregateKind::Count
152 | AggregateKind::Sum
153 | AggregateKind::Avg
154 | AggregateKind::Exists
155 | AggregateKind::Max
156 | AggregateKind::First
157 | AggregateKind::Last => Direction::Asc,
158 }
159 }
160
161 #[must_use]
163 pub(crate) const fn supports_bounded_probe_hint_for_kind(kind: AggregateKind) -> bool {
164 !Self::is_count_kind(kind) && !Self::is_sum_kind(kind)
165 }
166
167 #[must_use]
169 pub(crate) fn bounded_probe_fetch_hint_for_kind(
170 kind: AggregateKind,
171 direction: Direction,
172 offset: usize,
173 page_limit: Option<usize>,
174 ) -> Option<usize> {
175 match kind {
176 AggregateKind::Exists | AggregateKind::First => Some(offset.saturating_add(1)),
177 AggregateKind::Min if direction == Direction::Asc => Some(offset.saturating_add(1)),
178 AggregateKind::Max if direction == Direction::Desc => Some(offset.saturating_add(1)),
179 AggregateKind::Last => page_limit.map(|limit| offset.saturating_add(limit)),
180 AggregateKind::Count
181 | AggregateKind::Sum
182 | AggregateKind::Avg
183 | AggregateKind::Min
184 | AggregateKind::Max => None,
185 }
186 }
187}
188
189#[must_use]
191pub const fn count() -> AggregateExpr {
192 AggregateExpr::new(AggregateKind::Count, None)
193}
194
195#[must_use]
197pub fn count_by(field: impl AsRef<str>) -> AggregateExpr {
198 AggregateExpr::new(AggregateKind::Count, Some(field.as_ref().to_string()))
199}
200
201#[must_use]
203pub fn sum(field: impl AsRef<str>) -> AggregateExpr {
204 AggregateExpr::new(AggregateKind::Sum, Some(field.as_ref().to_string()))
205}
206
207#[must_use]
209pub fn avg(field: impl AsRef<str>) -> AggregateExpr {
210 AggregateExpr::new(AggregateKind::Avg, Some(field.as_ref().to_string()))
211}
212
213#[must_use]
215pub const fn exists() -> AggregateExpr {
216 AggregateExpr::new(AggregateKind::Exists, None)
217}
218
219#[must_use]
221pub const fn first() -> AggregateExpr {
222 AggregateExpr::new(AggregateKind::First, None)
223}
224
225#[must_use]
227pub const fn last() -> AggregateExpr {
228 AggregateExpr::new(AggregateKind::Last, None)
229}
230
231#[must_use]
233pub const fn min() -> AggregateExpr {
234 AggregateExpr::new(AggregateKind::Min, None)
235}
236
237#[must_use]
239pub fn min_by(field: impl AsRef<str>) -> AggregateExpr {
240 AggregateExpr::new(AggregateKind::Min, Some(field.as_ref().to_string()))
241}
242
243#[must_use]
245pub const fn max() -> AggregateExpr {
246 AggregateExpr::new(AggregateKind::Max, None)
247}
248
249#[must_use]
251pub fn max_by(field: impl AsRef<str>) -> AggregateExpr {
252 AggregateExpr::new(AggregateKind::Max, Some(field.as_ref().to_string()))
253}
254
255#[must_use]
257pub(crate) fn terminal_expr_for_kind(kind: AggregateKind) -> AggregateExpr {
258 match kind {
259 AggregateKind::Count => count(),
260 AggregateKind::Exists => exists(),
261 AggregateKind::Min => min(),
262 AggregateKind::Max => max(),
263 AggregateKind::First => first(),
264 AggregateKind::Last => last(),
265 AggregateKind::Sum | AggregateKind::Avg => {
266 unreachable!("terminal_expr_for_kind does not support SUM/AVG field-target kinds")
267 }
268 }
269}
270
271#[must_use]
273pub(crate) fn field_target_extrema_expr_for_kind(
274 kind: AggregateKind,
275 field: impl AsRef<str>,
276) -> AggregateExpr {
277 match kind {
278 AggregateKind::Min => min_by(field),
279 AggregateKind::Max => max_by(field),
280 _ => {
281 unreachable!(
282 "field_target_extrema_expr_for_kind requires MIN/MAX kind for field-target extrema"
283 )
284 }
285 }
286}