icydb_core/db/query/builder/
aggregate.rs1use crate::db::{direction::Direction, query::plan::GroupAggregateKind};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct AggregateExpr {
18 kind: GroupAggregateKind,
19 target_field: Option<String>,
20 distinct: bool,
21}
22
23impl AggregateExpr {
24 const fn new(kind: GroupAggregateKind, 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) -> GroupAggregateKind {
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 #[must_use]
60 pub(crate) const fn is_count_kind(kind: GroupAggregateKind) -> bool {
61 matches!(kind, GroupAggregateKind::Count)
62 }
63
64 #[must_use]
66 pub(crate) const fn is_sum_kind(kind: GroupAggregateKind) -> bool {
67 matches!(kind, GroupAggregateKind::Sum)
68 }
69
70 #[must_use]
72 pub(crate) const fn supports_field_targets_kind(kind: GroupAggregateKind) -> bool {
73 matches!(kind, GroupAggregateKind::Min | GroupAggregateKind::Max)
74 }
75
76 #[must_use]
78 pub(crate) const fn is_extrema_kind(kind: GroupAggregateKind) -> bool {
79 Self::supports_field_targets_kind(kind)
80 }
81
82 #[must_use]
84 pub(crate) const fn supports_terminal_value_projection_kind(kind: GroupAggregateKind) -> bool {
85 matches!(kind, GroupAggregateKind::First | GroupAggregateKind::Last)
86 }
87
88 #[must_use]
90 pub(crate) const fn requires_decoded_id_kind(kind: GroupAggregateKind) -> bool {
91 !matches!(
92 kind,
93 GroupAggregateKind::Count | GroupAggregateKind::Sum | GroupAggregateKind::Exists
94 )
95 }
96
97 #[must_use]
99 pub(crate) const fn supports_grouped_distinct_kind_v1(kind: GroupAggregateKind) -> bool {
100 matches!(
101 kind,
102 GroupAggregateKind::Count
103 | GroupAggregateKind::Min
104 | GroupAggregateKind::Max
105 | GroupAggregateKind::Sum
106 )
107 }
108
109 #[must_use]
111 pub(crate) const fn supports_global_distinct_without_group_keys_kind(
112 kind: GroupAggregateKind,
113 ) -> bool {
114 matches!(kind, GroupAggregateKind::Count | GroupAggregateKind::Sum)
115 }
116
117 #[must_use]
119 pub(crate) const fn fingerprint_tag_for_kind_v1(kind: GroupAggregateKind) -> u8 {
120 match kind {
121 GroupAggregateKind::Count => 0x01,
122 GroupAggregateKind::Sum => 0x02,
123 GroupAggregateKind::Exists => 0x03,
124 GroupAggregateKind::Min => 0x04,
125 GroupAggregateKind::Max => 0x05,
126 GroupAggregateKind::First => 0x06,
127 GroupAggregateKind::Last => 0x07,
128 }
129 }
130
131 #[must_use]
133 pub(crate) const fn extrema_direction_for_kind(kind: GroupAggregateKind) -> Option<Direction> {
134 match kind {
135 GroupAggregateKind::Min => Some(Direction::Asc),
136 GroupAggregateKind::Max => Some(Direction::Desc),
137 GroupAggregateKind::Count
138 | GroupAggregateKind::Sum
139 | GroupAggregateKind::Exists
140 | GroupAggregateKind::First
141 | GroupAggregateKind::Last => None,
142 }
143 }
144
145 #[must_use]
147 pub(crate) const fn materialized_fold_direction_for_kind(
148 kind: GroupAggregateKind,
149 ) -> Direction {
150 match kind {
151 GroupAggregateKind::Min => Direction::Desc,
152 GroupAggregateKind::Count
153 | GroupAggregateKind::Sum
154 | GroupAggregateKind::Exists
155 | GroupAggregateKind::Max
156 | GroupAggregateKind::First
157 | GroupAggregateKind::Last => Direction::Asc,
158 }
159 }
160
161 #[must_use]
163 pub(crate) const fn supports_bounded_probe_hint_for_kind(kind: GroupAggregateKind) -> 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: GroupAggregateKind,
171 direction: Direction,
172 offset: usize,
173 page_limit: Option<usize>,
174 ) -> Option<usize> {
175 match kind {
176 GroupAggregateKind::Exists | GroupAggregateKind::First => {
177 Some(offset.saturating_add(1))
178 }
179 GroupAggregateKind::Min if direction == Direction::Asc => {
180 Some(offset.saturating_add(1))
181 }
182 GroupAggregateKind::Max if direction == Direction::Desc => {
183 Some(offset.saturating_add(1))
184 }
185 GroupAggregateKind::Last => page_limit.map(|limit| offset.saturating_add(limit)),
186 GroupAggregateKind::Count
187 | GroupAggregateKind::Sum
188 | GroupAggregateKind::Min
189 | GroupAggregateKind::Max => None,
190 }
191 }
192}
193
194#[must_use]
196pub const fn count() -> AggregateExpr {
197 AggregateExpr::new(GroupAggregateKind::Count, None)
198}
199
200#[must_use]
202pub fn count_by(field: impl AsRef<str>) -> AggregateExpr {
203 AggregateExpr::new(GroupAggregateKind::Count, Some(field.as_ref().to_string()))
204}
205
206#[must_use]
208pub fn sum(field: impl AsRef<str>) -> AggregateExpr {
209 AggregateExpr::new(GroupAggregateKind::Sum, Some(field.as_ref().to_string()))
210}
211
212#[must_use]
214pub const fn exists() -> AggregateExpr {
215 AggregateExpr::new(GroupAggregateKind::Exists, None)
216}
217
218#[must_use]
220pub const fn first() -> AggregateExpr {
221 AggregateExpr::new(GroupAggregateKind::First, None)
222}
223
224#[must_use]
226pub const fn last() -> AggregateExpr {
227 AggregateExpr::new(GroupAggregateKind::Last, None)
228}
229
230#[must_use]
232pub const fn min() -> AggregateExpr {
233 AggregateExpr::new(GroupAggregateKind::Min, None)
234}
235
236#[must_use]
238pub fn min_by(field: impl AsRef<str>) -> AggregateExpr {
239 AggregateExpr::new(GroupAggregateKind::Min, Some(field.as_ref().to_string()))
240}
241
242#[must_use]
244pub const fn max() -> AggregateExpr {
245 AggregateExpr::new(GroupAggregateKind::Max, None)
246}
247
248#[must_use]
250pub fn max_by(field: impl AsRef<str>) -> AggregateExpr {
251 AggregateExpr::new(GroupAggregateKind::Max, Some(field.as_ref().to_string()))
252}