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 supports_terminal_value_projection_kind(kind: AggregateKind) -> bool {
101 matches!(kind, AggregateKind::First | AggregateKind::Last)
102 }
103
104 #[must_use]
106 pub(crate) const fn requires_decoded_id_kind(kind: AggregateKind) -> bool {
107 !matches!(
108 kind,
109 AggregateKind::Count | AggregateKind::Sum | AggregateKind::Avg | AggregateKind::Exists
110 )
111 }
112
113 #[must_use]
115 pub(crate) const fn supports_grouped_distinct_kind_v1(kind: AggregateKind) -> bool {
116 matches!(
117 kind,
118 AggregateKind::Count
119 | AggregateKind::Min
120 | AggregateKind::Max
121 | AggregateKind::Sum
122 | AggregateKind::Avg
123 )
124 }
125
126 #[must_use]
128 pub(crate) const fn supports_global_distinct_without_group_keys_kind(
129 kind: AggregateKind,
130 ) -> bool {
131 matches!(
132 kind,
133 AggregateKind::Count | AggregateKind::Sum | AggregateKind::Avg
134 )
135 }
136
137 #[must_use]
139 pub(crate) const fn extrema_direction_for_kind(kind: AggregateKind) -> Option<Direction> {
140 match kind {
141 AggregateKind::Min => Some(Direction::Asc),
142 AggregateKind::Max => Some(Direction::Desc),
143 AggregateKind::Count
144 | AggregateKind::Sum
145 | AggregateKind::Avg
146 | AggregateKind::Exists
147 | AggregateKind::First
148 | AggregateKind::Last => None,
149 }
150 }
151
152 #[must_use]
154 pub(crate) const fn materialized_fold_direction_for_kind(kind: AggregateKind) -> Direction {
155 match kind {
156 AggregateKind::Min => Direction::Desc,
157 AggregateKind::Count
158 | AggregateKind::Sum
159 | AggregateKind::Avg
160 | AggregateKind::Exists
161 | AggregateKind::Max
162 | AggregateKind::First
163 | AggregateKind::Last => Direction::Asc,
164 }
165 }
166
167 #[must_use]
169 pub(crate) const fn supports_bounded_probe_hint_for_kind(kind: AggregateKind) -> bool {
170 !Self::is_count_kind(kind) && !Self::is_sum_kind(kind)
171 }
172
173 #[must_use]
175 pub(crate) fn bounded_probe_fetch_hint_for_kind(
176 kind: AggregateKind,
177 direction: Direction,
178 offset: usize,
179 page_limit: Option<usize>,
180 ) -> Option<usize> {
181 match kind {
182 AggregateKind::Exists | AggregateKind::First => Some(offset.saturating_add(1)),
183 AggregateKind::Min if direction == Direction::Asc => Some(offset.saturating_add(1)),
184 AggregateKind::Max if direction == Direction::Desc => Some(offset.saturating_add(1)),
185 AggregateKind::Last => page_limit.map(|limit| offset.saturating_add(limit)),
186 AggregateKind::Count
187 | AggregateKind::Sum
188 | AggregateKind::Avg
189 | AggregateKind::Min
190 | AggregateKind::Max => None,
191 }
192 }
193}
194
195#[must_use]
197pub const fn count() -> AggregateExpr {
198 AggregateExpr::new(AggregateKind::Count, None)
199}
200
201#[must_use]
203pub fn count_by(field: impl AsRef<str>) -> AggregateExpr {
204 AggregateExpr::new(AggregateKind::Count, Some(field.as_ref().to_string()))
205}
206
207#[must_use]
209pub fn sum(field: impl AsRef<str>) -> AggregateExpr {
210 AggregateExpr::new(AggregateKind::Sum, Some(field.as_ref().to_string()))
211}
212
213#[must_use]
215pub fn avg(field: impl AsRef<str>) -> AggregateExpr {
216 AggregateExpr::new(AggregateKind::Avg, Some(field.as_ref().to_string()))
217}
218
219#[must_use]
221pub const fn exists() -> AggregateExpr {
222 AggregateExpr::new(AggregateKind::Exists, None)
223}
224
225#[must_use]
227pub const fn first() -> AggregateExpr {
228 AggregateExpr::new(AggregateKind::First, None)
229}
230
231#[must_use]
233pub const fn last() -> AggregateExpr {
234 AggregateExpr::new(AggregateKind::Last, None)
235}
236
237#[must_use]
239pub const fn min() -> AggregateExpr {
240 AggregateExpr::new(AggregateKind::Min, None)
241}
242
243#[must_use]
245pub fn min_by(field: impl AsRef<str>) -> AggregateExpr {
246 AggregateExpr::new(AggregateKind::Min, Some(field.as_ref().to_string()))
247}
248
249#[must_use]
251pub const fn max() -> AggregateExpr {
252 AggregateExpr::new(AggregateKind::Max, None)
253}
254
255#[must_use]
257pub fn max_by(field: impl AsRef<str>) -> AggregateExpr {
258 AggregateExpr::new(AggregateKind::Max, Some(field.as_ref().to_string()))
259}