zeppelin_embed/lifecycle/
group_count.rs1use std::collections::BTreeMap;
4use std::collections::btree_map::Entry;
5
6use crate::meta::{Column, ColumnId, ColumnStore, ColumnType, DocBitmap};
7
8use super::{AdmittedDocumentRead, QueryError, Store, StoreError};
9
10pub const MAX_DOCUMENT_GROUP_LIMIT: usize = 1 << 16;
12
13#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
15pub enum DocumentGroupValue {
16 U64(u64),
18 I64(i64),
20 String(String),
22}
23
24#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct DocumentGroup {
27 pub value: DocumentGroupValue,
29 pub count: u64,
31}
32
33#[derive(Clone, Debug, Eq, PartialEq)]
35pub struct DocumentGroupCounts {
36 pub groups: Vec<DocumentGroup>,
39 pub missing: u64,
41 pub count: u64,
43 pub generation: u64,
45}
46
47enum Tally {
48 U64(BTreeMap<u64, u64>),
49 I64(BTreeMap<i64, u64>),
50 String(BTreeMap<String, u64>),
51}
52
53impl Store {
54 pub fn count_documents_grouped(
63 &self,
64 predicate: Option<&crate::meta::Predicate>,
65 timestamp_range: Option<(i64, i64)>,
66 column: ColumnId,
67 limit: usize,
68 ) -> Result<DocumentGroupCounts, QueryError> {
69 let invalid = |detail: String| QueryError::Store(StoreError::InvalidScan { detail });
70 if limit == 0 || limit > MAX_DOCUMENT_GROUP_LIMIT {
71 return Err(invalid(format!(
72 "group limit must be in 1..={MAX_DOCUMENT_GROUP_LIMIT}, received {limit}"
73 )));
74 }
75 let definition = self.schema.column(column).ok_or_else(|| {
76 invalid(format!(
77 "group-by attribute {} is not in the schema",
78 column.get()
79 ))
80 })?;
81 let nullable = definition.is_nullable();
82 let mut tally = match definition.column_type() {
83 ColumnType::U64 => Tally::U64(BTreeMap::new()),
84 ColumnType::I64 => Tally::I64(BTreeMap::new()),
85 ColumnType::DictionaryString | ColumnType::RawString => Tally::String(BTreeMap::new()),
86 other @ (ColumnType::Id128 | ColumnType::F64 | ColumnType::Bool) => {
87 return Err(invalid(format!(
88 "group-by attribute {} has type {other:?}; grouping supports \
89 U64, I64, DictionaryString and RawString",
90 column.get()
91 )));
92 }
93 };
94 let predicate = super::document_scan_predicate(predicate, timestamp_range)?;
95 if let Some(predicate) = &predicate {
96 crate::planner::validate_predicate(predicate, &self.schema)
97 .map_err(|error| invalid(error.to_string()))?;
98 }
99 let AdmittedDocumentRead {
100 generation,
101 active,
102 snapshot,
103 active_query,
104 } = self.admit_document_read().map_err(QueryError::Store)?;
105 let mut missing = 0_u64;
106 for segment in snapshot.segments() {
107 let rows = super::sealed_scan_rows(segment, predicate.as_ref())?;
108 if rows.is_empty() {
109 continue;
110 }
111 let columns = segment.query_columns().map_err(QueryError::Store)?;
112 missing = add(
113 missing,
114 tally.add_rows(&columns, &rows, column, nullable, limit)?,
115 )?;
116 }
117 let columns = super::active_scan_columns(&active, &self.schema)?;
118 let alive = active.alive().map_err(QueryError::Store)?;
119 let rows = match &predicate {
120 Some(predicate) => {
121 crate::meta::evaluate(predicate, &columns, &alive).map_err(|error| {
122 QueryError::Store(StoreError::Segment(crate::segment::SegmentError::Columns(
123 error.to_string(),
124 )))
125 })?
126 }
127 None => alive.alive_bitmap().clone(),
128 };
129 missing = add(
130 missing,
131 tally.add_rows(&columns, &rows, column, nullable, limit)?,
132 )?;
133 drop(active_query);
134 let groups = tally.into_groups()?;
135 let count = groups
136 .iter()
137 .try_fold(missing, |total, group| add(total, group.count))?;
138 Ok(DocumentGroupCounts {
139 groups,
140 missing,
141 count,
142 generation,
143 })
144 }
145}
146
147impl Tally {
148 fn add_rows(
150 &mut self,
151 columns: &ColumnStore,
152 rows: &DocBitmap,
153 column: ColumnId,
154 nullable: bool,
155 limit: usize,
156 ) -> Result<u64, QueryError> {
157 let values = columns.column(column).ok_or_else(|| {
158 corrupt(format!(
159 "group-by attribute {} is missing from a segment",
160 column.get()
161 ))
162 })?;
163 let mut missing = 0_u64;
164 for row in rows.iter() {
165 let present = match (&mut *self, values) {
166 (Self::U64(map), Column::U64(values)) => values
167 .get(row)
168 .map(|value| bump(map, value, limit))
169 .transpose()?,
170 (Self::I64(map), Column::I64(values)) => values
171 .get(row)
172 .map(|value| bump(map, value, limit))
173 .transpose()?,
174 (Self::String(map), Column::DictionaryString(values)) => values
175 .get(row)
176 .map(|value| bump_string(map, value, limit))
177 .transpose()?,
178 (Self::String(map), Column::RawString(values)) => values
179 .get(row)
180 .map(|value| bump_string(map, value, limit))
181 .transpose()?,
182 (_, values) => {
183 return Err(corrupt(format!(
184 "group-by attribute {} is stored as {:?}",
185 column.get(),
186 values.column_type()
187 )));
188 }
189 };
190 if present.is_none() {
191 if !nullable {
192 return Err(corrupt(format!(
193 "required group-by attribute {} is null at row {row}",
194 column.get()
195 )));
196 }
197 missing = add(missing, 1)?;
198 }
199 }
200 Ok(missing)
201 }
202
203 fn into_groups(self) -> Result<Vec<DocumentGroup>, QueryError> {
204 fn collect<K>(
205 map: BTreeMap<K, u64>,
206 value: impl Fn(K) -> DocumentGroupValue,
207 ) -> Result<Vec<DocumentGroup>, QueryError> {
208 let mut groups = Vec::new();
209 groups.try_reserve_exact(map.len()).map_err(|_| {
210 QueryError::Store(StoreError::AllocationFailed {
211 needed: super::allocation_bytes::<DocumentGroup>(map.len()),
212 component: "grouped count groups",
213 })
214 })?;
215 groups.extend(map.into_iter().map(|(key, count)| DocumentGroup {
216 value: value(key),
217 count,
218 }));
219 Ok(groups)
220 }
221 match self {
222 Self::U64(map) => collect(map, DocumentGroupValue::U64),
223 Self::I64(map) => collect(map, DocumentGroupValue::I64),
224 Self::String(map) => collect(map, DocumentGroupValue::String),
225 }
226 }
227}
228
229fn bump<K: Ord>(map: &mut BTreeMap<K, u64>, key: K, limit: usize) -> Result<(), QueryError> {
230 let len = map.len();
231 match map.entry(key) {
232 Entry::Occupied(mut entry) => {
233 *entry.get_mut() = add(*entry.get(), 1)?;
234 }
235 Entry::Vacant(_) if len >= limit => {
236 return Err(QueryError::Store(StoreError::GroupLimitExceeded { limit }));
237 }
238 Entry::Vacant(entry) => {
239 entry.insert(1);
240 }
241 }
242 Ok(())
243}
244
245fn bump_string(map: &mut BTreeMap<String, u64>, key: &str, limit: usize) -> Result<(), QueryError> {
246 if let Some(count) = map.get_mut(key) {
247 *count = add(*count, 1)?;
248 return Ok(());
249 }
250 if map.len() >= limit {
251 return Err(QueryError::Store(StoreError::GroupLimitExceeded { limit }));
252 }
253 let key = super::copy_string(key, "grouped count value").map_err(QueryError::Store)?;
254 map.insert(key, 1);
255 Ok(())
256}
257
258fn add(total: u64, count: u64) -> Result<u64, QueryError> {
259 total
260 .checked_add(count)
261 .ok_or(QueryError::Scan(crate::scan::ScanError::ArithmeticOverflow))
262}
263
264fn corrupt(detail: String) -> QueryError {
265 QueryError::Store(StoreError::Segment(crate::segment::SegmentError::Geometry(
266 detail,
267 )))
268}