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