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 pub(in crate::db::query) const fn from_semantic_parts(
60 kind: GroupAggregateKind,
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: GroupAggregateKind) -> bool {
74 matches!(kind, GroupAggregateKind::Count)
75 }
76
77 #[must_use]
79 pub(crate) const fn is_sum_kind(kind: GroupAggregateKind) -> bool {
80 matches!(kind, GroupAggregateKind::Sum)
81 }
82
83 #[must_use]
85 pub(crate) const fn supports_field_targets_kind(kind: GroupAggregateKind) -> bool {
86 matches!(kind, GroupAggregateKind::Min | GroupAggregateKind::Max)
87 }
88
89 #[must_use]
91 pub(crate) const fn is_extrema_kind(kind: GroupAggregateKind) -> bool {
92 Self::supports_field_targets_kind(kind)
93 }
94
95 #[must_use]
97 pub(crate) const fn supports_terminal_value_projection_kind(kind: GroupAggregateKind) -> bool {
98 matches!(kind, GroupAggregateKind::First | GroupAggregateKind::Last)
99 }
100
101 #[must_use]
103 pub(crate) const fn requires_decoded_id_kind(kind: GroupAggregateKind) -> bool {
104 !matches!(
105 kind,
106 GroupAggregateKind::Count | GroupAggregateKind::Sum | GroupAggregateKind::Exists
107 )
108 }
109
110 #[must_use]
112 pub(crate) const fn supports_grouped_distinct_kind_v1(kind: GroupAggregateKind) -> bool {
113 matches!(
114 kind,
115 GroupAggregateKind::Count
116 | GroupAggregateKind::Min
117 | GroupAggregateKind::Max
118 | GroupAggregateKind::Sum
119 )
120 }
121
122 #[must_use]
124 pub(crate) const fn supports_global_distinct_without_group_keys_kind(
125 kind: GroupAggregateKind,
126 ) -> bool {
127 matches!(kind, GroupAggregateKind::Count | GroupAggregateKind::Sum)
128 }
129
130 #[must_use]
132 pub(crate) const fn extrema_direction_for_kind(kind: GroupAggregateKind) -> Option<Direction> {
133 match kind {
134 GroupAggregateKind::Min => Some(Direction::Asc),
135 GroupAggregateKind::Max => Some(Direction::Desc),
136 GroupAggregateKind::Count
137 | GroupAggregateKind::Sum
138 | GroupAggregateKind::Exists
139 | GroupAggregateKind::First
140 | GroupAggregateKind::Last => None,
141 }
142 }
143
144 #[must_use]
146 pub(crate) const fn materialized_fold_direction_for_kind(
147 kind: GroupAggregateKind,
148 ) -> Direction {
149 match kind {
150 GroupAggregateKind::Min => Direction::Desc,
151 GroupAggregateKind::Count
152 | GroupAggregateKind::Sum
153 | GroupAggregateKind::Exists
154 | GroupAggregateKind::Max
155 | GroupAggregateKind::First
156 | GroupAggregateKind::Last => Direction::Asc,
157 }
158 }
159
160 #[must_use]
162 pub(crate) const fn supports_bounded_probe_hint_for_kind(kind: GroupAggregateKind) -> bool {
163 !Self::is_count_kind(kind) && !Self::is_sum_kind(kind)
164 }
165
166 #[must_use]
168 pub(crate) fn bounded_probe_fetch_hint_for_kind(
169 kind: GroupAggregateKind,
170 direction: Direction,
171 offset: usize,
172 page_limit: Option<usize>,
173 ) -> Option<usize> {
174 match kind {
175 GroupAggregateKind::Exists | GroupAggregateKind::First => {
176 Some(offset.saturating_add(1))
177 }
178 GroupAggregateKind::Min if direction == Direction::Asc => {
179 Some(offset.saturating_add(1))
180 }
181 GroupAggregateKind::Max if direction == Direction::Desc => {
182 Some(offset.saturating_add(1))
183 }
184 GroupAggregateKind::Last => page_limit.map(|limit| offset.saturating_add(limit)),
185 GroupAggregateKind::Count
186 | GroupAggregateKind::Sum
187 | GroupAggregateKind::Min
188 | GroupAggregateKind::Max => None,
189 }
190 }
191}
192
193#[must_use]
195pub const fn count() -> AggregateExpr {
196 AggregateExpr::new(GroupAggregateKind::Count, None)
197}
198
199#[must_use]
201pub fn count_by(field: impl AsRef<str>) -> AggregateExpr {
202 AggregateExpr::new(GroupAggregateKind::Count, Some(field.as_ref().to_string()))
203}
204
205#[must_use]
207pub fn sum(field: impl AsRef<str>) -> AggregateExpr {
208 AggregateExpr::new(GroupAggregateKind::Sum, Some(field.as_ref().to_string()))
209}
210
211#[must_use]
213pub const fn exists() -> AggregateExpr {
214 AggregateExpr::new(GroupAggregateKind::Exists, None)
215}
216
217#[must_use]
219pub const fn first() -> AggregateExpr {
220 AggregateExpr::new(GroupAggregateKind::First, None)
221}
222
223#[must_use]
225pub const fn last() -> AggregateExpr {
226 AggregateExpr::new(GroupAggregateKind::Last, None)
227}
228
229#[must_use]
231pub const fn min() -> AggregateExpr {
232 AggregateExpr::new(GroupAggregateKind::Min, None)
233}
234
235#[must_use]
237pub fn min_by(field: impl AsRef<str>) -> AggregateExpr {
238 AggregateExpr::new(GroupAggregateKind::Min, Some(field.as_ref().to_string()))
239}
240
241#[must_use]
243pub const fn max() -> AggregateExpr {
244 AggregateExpr::new(GroupAggregateKind::Max, None)
245}
246
247#[must_use]
249pub fn max_by(field: impl AsRef<str>) -> AggregateExpr {
250 AggregateExpr::new(GroupAggregateKind::Max, Some(field.as_ref().to_string()))
251}