1use std::collections::HashMap;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum DictionaryError {
8 CardinalityOverflow,
10 StringStorageOverflow,
12}
13
14impl std::fmt::Display for DictionaryError {
15 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 match self {
17 Self::CardinalityOverflow => formatter.write_str("dictionary cardinality exceeds u32"),
18 Self::StringStorageOverflow => formatter.write_str("string storage exceeds u32"),
19 }
20 }
21}
22
23impl std::error::Error for DictionaryError {}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub enum CodeWidth {
28 U16,
30 U32,
32}
33
34#[derive(Clone, Debug, Eq, PartialEq)]
36pub enum DictionaryCodes {
37 U16(Vec<u16>),
39 U32(Vec<u32>),
41}
42
43impl DictionaryCodes {
44 pub(crate) fn resident_bytes(&self) -> Option<usize> {
45 match self {
46 Self::U16(codes) => codes.capacity().checked_mul(std::mem::size_of::<u16>()),
47 Self::U32(codes) => codes.capacity().checked_mul(std::mem::size_of::<u32>()),
48 }
49 }
50
51 #[must_use]
53 pub const fn width(&self) -> CodeWidth {
54 match self {
55 Self::U16(_) => CodeWidth::U16,
56 Self::U32(_) => CodeWidth::U32,
57 }
58 }
59
60 #[must_use]
62 pub fn len(&self) -> usize {
63 match self {
64 Self::U16(codes) => codes.len(),
65 Self::U32(codes) => codes.len(),
66 }
67 }
68
69 #[must_use]
71 pub fn is_empty(&self) -> bool {
72 self.len() == 0
73 }
74
75 #[must_use]
77 pub fn get(&self, row: usize) -> Option<u32> {
78 match self {
79 Self::U16(codes) => codes.get(row).copied().map(u32::from),
80 Self::U32(codes) => codes.get(row).copied(),
81 }
82 }
83
84 pub(crate) fn iter(&self) -> impl Iterator<Item = u32> + '_ {
85 enum Iter<'a> {
86 U16(std::slice::Iter<'a, u16>),
87 U32(std::slice::Iter<'a, u32>),
88 }
89
90 impl Iterator for Iter<'_> {
91 type Item = u32;
92
93 fn next(&mut self) -> Option<Self::Item> {
94 match self {
95 Iter::U16(values) => values.next().copied().map(u32::from),
96 Iter::U32(values) => values.next().copied(),
97 }
98 }
99 }
100
101 match self {
102 Self::U16(values) => Iter::U16(values.iter()),
103 Self::U32(values) => Iter::U32(values.iter()),
104 }
105 }
106
107 pub(crate) fn from_u32(codes: Vec<u32>, cardinality: u64) -> Result<Self, DictionaryError> {
108 match code_width_for_cardinality(cardinality)? {
109 CodeWidth::U16 => {
110 let narrowed = codes
111 .into_iter()
112 .map(u16::try_from)
113 .collect::<Result<Vec<_>, _>>()
114 .map_err(|_| DictionaryError::CardinalityOverflow)?;
115 Ok(Self::U16(narrowed))
116 }
117 CodeWidth::U32 => Ok(Self::U32(codes)),
118 }
119 }
120}
121
122pub(crate) fn code_width_for_cardinality(cardinality: u64) -> Result<CodeWidth, DictionaryError> {
123 if cardinality <= u64::from(u16::MAX) {
124 Ok(CodeWidth::U16)
125 } else if cardinality <= u64::from(u32::MAX) {
126 Ok(CodeWidth::U32)
127 } else {
128 Err(DictionaryError::CardinalityOverflow)
129 }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq)]
133#[repr(C)]
134pub(crate) struct StringStorage {
135 offsets: Vec<u32>,
136 bytes: Vec<u8>,
137}
138
139impl StringStorage {
140 pub(crate) fn resident_bytes(&self) -> Option<usize> {
141 self.offsets
142 .capacity()
143 .checked_mul(std::mem::size_of::<u32>())?
144 .checked_add(self.bytes.capacity())
145 }
146 pub(crate) fn new() -> Self {
147 Self {
148 offsets: vec![0],
149 bytes: Vec::new(),
150 }
151 }
152
153 pub(crate) fn can_push(&self, value: &str) -> Result<(), DictionaryError> {
154 let new_length = self
155 .bytes
156 .len()
157 .checked_add(value.len())
158 .ok_or(DictionaryError::StringStorageOverflow)?;
159 u32::try_from(new_length)
160 .map(|_| ())
161 .map_err(|_| DictionaryError::StringStorageOverflow)
162 }
163
164 pub(crate) fn push(&mut self, value: &str) -> Result<(), DictionaryError> {
165 self.can_push(value)?;
166 self.bytes.extend_from_slice(value.as_bytes());
167 let offset =
168 u32::try_from(self.bytes.len()).map_err(|_| DictionaryError::StringStorageOverflow)?;
169 self.offsets.push(offset);
170 Ok(())
171 }
172
173 pub(crate) fn len(&self) -> usize {
174 self.offsets.len().saturating_sub(1)
175 }
176
177 #[cfg(any(test, feature = "test-seams"))]
178 pub(crate) fn byte_len(&self) -> usize {
179 self.bytes.len()
180 }
181
182 pub(crate) fn get(&self, position: usize) -> Option<&str> {
183 let start = self
184 .offsets
185 .get(position)
186 .copied()
187 .map(|value| value as usize)?;
188 let end = self
189 .offsets
190 .get(position.saturating_add(1))
191 .copied()
192 .map(|value| value as usize)?;
193 let bytes = self.bytes.get(start..end)?;
194 std::str::from_utf8(bytes).ok()
195 }
196}
197
198#[derive(Clone, Debug, Eq, PartialEq)]
200#[repr(C)]
201pub struct StringDictionary {
202 values: StringStorage,
203}
204
205impl StringDictionary {
206 pub(crate) fn resident_bytes(&self) -> Option<usize> {
207 self.values.resident_bytes()
208 }
209
210 #[must_use]
212 pub fn len(&self) -> usize {
213 self.values.len()
214 }
215
216 #[must_use]
218 pub fn is_empty(&self) -> bool {
219 self.len() == 0
220 }
221
222 #[must_use]
224 pub fn get(&self, code: u32) -> Option<&str> {
225 let position = usize::try_from(code).ok()?;
226 self.values.get(position)
227 }
228
229 #[must_use]
231 pub fn find(&self, needle: &str) -> Option<u32> {
232 self.values
233 .offsets
234 .windows(2)
235 .enumerate()
236 .find_map(|(position, _)| {
237 (self.values.get(position) == Some(needle))
238 .then(|| u32::try_from(position).ok())
239 .flatten()
240 })
241 }
242}
243
244pub(crate) struct DictionaryBuilder {
245 lookup: HashMap<String, u32>,
246 values: StringStorage,
247}
248
249impl DictionaryBuilder {
250 pub(crate) fn new() -> Self {
251 Self {
252 lookup: HashMap::new(),
253 values: StringStorage::new(),
254 }
255 }
256
257 pub(crate) fn can_intern(&self, value: &str) -> Result<(), DictionaryError> {
258 if self.lookup.contains_key(value) {
259 return Ok(());
260 }
261 let cardinality = u64::try_from(self.lookup.len())
262 .unwrap_or(u64::MAX)
263 .saturating_add(1);
264 code_width_for_cardinality(cardinality)?;
265 self.values.can_push(value)
266 }
267
268 pub(crate) fn intern(&mut self, value: &str) -> Result<u32, DictionaryError> {
269 if let Some(code) = self.lookup.get(value).copied() {
270 return Ok(code);
271 }
272 self.can_intern(value)?;
273 let code =
274 u32::try_from(self.lookup.len()).map_err(|_| DictionaryError::CardinalityOverflow)?;
275 self.values.push(value)?;
276 self.lookup.insert(value.to_owned(), code);
277 Ok(code)
278 }
279
280 pub(crate) fn cardinality(&self) -> u64 {
281 u64::try_from(self.lookup.len()).unwrap_or(u64::MAX)
282 }
283
284 #[cfg(any(test, feature = "test-seams"))]
285 pub(crate) fn contains(&self, value: &str) -> bool {
286 self.lookup.contains_key(value)
287 }
288
289 #[cfg(any(test, feature = "test-seams"))]
290 pub(crate) fn string_bytes(&self) -> usize {
291 self.values.byte_len()
292 }
293
294 pub(crate) fn finish(self) -> StringDictionary {
295 StringDictionary {
296 values: self.values,
297 }
298 }
299}
300
301#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use proptest::prelude::*;
306 use proptest::test_runner::{Config, RngSeed, TestRunner};
307 use rand::RngCore;
308
309 #[test]
310 fn prop_dict_roundtrip() {
311 let name = "meta::dict::prop_dict_roundtrip";
312 let mut seeded = crate::test_support::seeded_rng(name);
313 let config = Config {
314 rng_seed: RngSeed::Fixed(seeded.next_u64()),
315 ..Config::default()
316 };
317 let mut runner = TestRunner::new(config);
318 let values = prop::collection::vec("[a-z]{0,16}", 0..=256);
319 let result = runner.run(&values, |values| {
320 let mut builder = DictionaryBuilder::new();
321 let codes = values
322 .iter()
323 .map(|value| builder.intern(value).expect("bounded dictionary"))
324 .collect::<Vec<_>>();
325 let dictionary = builder.finish();
326 let decoded = codes
327 .into_iter()
328 .map(|code| dictionary.get(code).map(str::to_owned))
329 .collect::<Vec<_>>();
330 let expected = values.into_iter().map(Some).collect::<Vec<_>>();
331 prop_assert_eq!(decoded, expected);
332 Ok(())
333 });
334 assert!(result.is_ok(), "property result: {result:?}");
335 }
336
337 #[test]
338 fn dictionary_auto_widens_u16_to_u32() {
339 let cardinality = u64::from(u16::MAX).saturating_add(1);
340 let codes = (0..=u32::from(u16::MAX)).collect::<Vec<_>>();
341 let encoded = DictionaryCodes::from_u32(codes, cardinality).expect("u32 cardinality");
342 assert_eq!(encoded.width(), CodeWidth::U32);
343 }
344
345 #[test]
346 fn dictionary_reports_u32_cardinality_overflow() {
347 assert_eq!(
348 code_width_for_cardinality(u64::from(u32::MAX).saturating_add(1)),
349 Err(DictionaryError::CardinalityOverflow)
350 );
351 }
352}