akar_storage/
string_dictionary.rs1use std::collections::HashMap;
8use std::rc::Rc;
9
10#[derive(Debug, Clone)]
12pub struct StringDictionary {
13 strings: Vec<Rc<str>>,
17 lookup: HashMap<Rc<str>, u32>,
20}
21
22impl StringDictionary {
23 pub fn new() -> Self {
25 Self {
26 strings: Vec::new(),
27 lookup: HashMap::new(),
28 }
29 }
30
31 pub fn encode(strings: &[Option<&str>]) -> (Self, Vec<u32>) {
37 let mut dict = Self::new();
38 let mut ids = Vec::with_capacity(strings.len());
39 for s in strings {
40 let id = match s {
41 None => u32::MAX,
42 Some(val) => dict.intern(val),
43 };
44 ids.push(id);
45 }
46 (dict, ids)
47 }
48
49 pub fn intern(&mut self, s: &str) -> u32 {
51 if let Some(&id) = self.lookup.get(s) {
52 return id;
53 }
54 let id = self.strings.len() as u32;
55 let owned: Rc<str> = Rc::from(s);
56 self.strings.push(Rc::clone(&owned));
57 self.lookup.insert(owned, id);
58 id
59 }
60
61 pub fn lookup(&self, id: u32) -> Option<&str> {
63 self.strings.get(id as usize).map(|s| s.as_ref())
64 }
65
66 pub fn lookup_id(&self, s: &str) -> Option<u32> {
68 self.lookup.get(s).copied()
69 }
70
71 pub fn len(&self) -> usize {
73 self.strings.len()
74 }
75
76 pub fn is_empty(&self) -> bool {
78 self.strings.is_empty()
79 }
80
81 pub fn serialize(&self) -> Vec<u8> {
85 let mut buf = Vec::new();
86 buf.extend_from_slice(&(self.strings.len() as u32).to_le_bytes());
87 for s in &self.strings {
88 buf.extend_from_slice(&(s.len() as u32).to_le_bytes());
89 buf.extend_from_slice(s.as_bytes());
90 }
91 buf
92 }
93
94 pub fn deserialize(data: &[u8]) -> std::io::Result<Self> {
96 if data.len() < 4 {
97 return Err(std::io::Error::new(
98 std::io::ErrorKind::UnexpectedEof,
99 "data too short for dictionary header",
100 ));
101 }
102 let num_strings = u32::from_le_bytes(data[..4].try_into().unwrap()) as usize;
103 let mut dict = Self::with_capacity(num_strings);
104 let mut offset = 4usize;
105 for _ in 0..num_strings {
106 if offset + 4 > data.len() {
107 return Err(std::io::Error::new(
108 std::io::ErrorKind::UnexpectedEof,
109 "data too short for string length",
110 ));
111 }
112 let len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
113 offset += 4;
114 if offset + len > data.len() {
115 return Err(std::io::Error::new(
116 std::io::ErrorKind::UnexpectedEof,
117 "data too short for string content",
118 ));
119 }
120 let s = String::from_utf8(data[offset..offset + len].to_vec())
121 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
122 offset += len;
123 let id = dict.strings.len() as u32;
124 let owned: Rc<str> = Rc::from(s);
125 dict.strings.push(Rc::clone(&owned));
126 dict.lookup.insert(owned, id);
127 }
128 Ok(dict)
129 }
130
131 pub fn memory_usage(&self) -> usize {
137 let mut total = std::mem::size_of::<Self>();
138 total += self.strings.capacity() * std::mem::size_of::<Rc<str>>();
139 for s in &self.strings {
140 total += std::mem::size_of::<Rc<str>>() + s.len();
141 }
142 total += self.lookup.capacity() * std::mem::size_of::<(Rc<str>, u32)>();
143 total
144 }
145
146 fn with_capacity(cap: usize) -> Self {
147 Self {
148 strings: Vec::with_capacity(cap),
149 lookup: HashMap::with_capacity(cap),
150 }
151 }
152}
153
154impl Default for StringDictionary {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_encode_decode_roundtrip() {
166 let input = [
167 Some("apple"),
168 Some("banana"),
169 Some("apple"),
170 Some("cherry"),
171 Some("banana"),
172 ];
173 let (dict, ids) = StringDictionary::encode(&input);
174 assert_eq!(dict.len(), 3);
175 for (i, s) in input.iter().enumerate() {
176 let expected = ids[i];
177 if let Some(val) = s {
178 assert_eq!(dict.lookup(expected), Some(*val));
179 }
180 }
181 }
182
183 #[test]
184 fn test_intern_dedup() {
185 let mut dict = StringDictionary::new();
186 let id1 = dict.intern("hello");
187 let id2 = dict.intern("hello");
188 assert_eq!(id1, id2);
189 let id3 = dict.intern("world");
190 assert_ne!(id1, id3);
191 assert_eq!(dict.len(), 2);
192 }
193
194 #[test]
195 fn test_serialize_roundtrip() {
196 let mut dict = StringDictionary::new();
197 dict.intern("alpha");
198 dict.intern("beta");
199 dict.intern("gamma");
200 let bytes = dict.serialize();
201 let deserialized = StringDictionary::deserialize(&bytes).unwrap();
202 assert_eq!(deserialized.len(), dict.len());
203 assert_eq!(deserialized.lookup_id("alpha"), Some(0));
204 assert_eq!(deserialized.lookup_id("beta"), Some(1));
205 assert_eq!(deserialized.lookup_id("gamma"), Some(2));
206 assert_eq!(deserialized.lookup_id("delta"), None);
207 }
208
209 #[test]
210 fn test_empty_input() {
211 let input: &[Option<&str>] = &[];
212 let (dict, ids) = StringDictionary::encode(input);
213 assert!(dict.is_empty());
214 assert!(ids.is_empty());
215 }
216
217 #[test]
218 fn test_null_handling() {
219 let input = [Some("a"), None, Some("b"), None];
220 let (dict, ids) = StringDictionary::encode(&input);
221 assert_eq!(dict.len(), 2);
222 assert_eq!(ids[0], 0);
223 assert_eq!(ids[1], u32::MAX);
224 assert_eq!(ids[2], 1);
225 assert_eq!(ids[3], u32::MAX);
226 }
227
228 #[test]
229 fn test_lookup_miss() {
230 let mut dict = StringDictionary::new();
231 dict.intern("foo");
232 assert_eq!(dict.lookup(0), Some("foo"));
233 assert_eq!(dict.lookup(1), None);
234 assert_eq!(dict.lookup(u32::MAX), None);
235 assert_eq!(dict.lookup_id("bar"), None);
236 }
237
238 #[test]
239 fn test_memory_usage() {
240 let mut dict = StringDictionary::new();
241 dict.intern("short");
242 dict.intern("a longer string value");
243 let usage = dict.memory_usage();
244 assert!(usage > 0);
245 assert!(usage > std::mem::size_of::<StringDictionary>());
246 }
247
248 #[test]
249 fn test_deserialize_empty() {
250 let bytes = 0u32.to_le_bytes().to_vec();
251 let dict = StringDictionary::deserialize(&bytes).unwrap();
252 assert!(dict.is_empty());
253 assert_eq!(dict.len(), 0);
254 }
255
256 #[test]
257 fn test_deserialize_truncated() {
258 let result = StringDictionary::deserialize(&[1, 0, 0, 0]);
259 assert!(result.is_err());
260 }
261
262 #[test]
263 fn test_deserialize_invalid_utf8() {
264 let bytes = [1u8, 0, 0, 0, 1, 0, 0, 0, 0xFF];
265 let result = StringDictionary::deserialize(&bytes);
266 assert!(result.is_err());
267 }
268
269 #[test]
270 fn test_empty_string() {
271 let mut dict = StringDictionary::new();
272 let id = dict.intern("");
273 assert_eq!(dict.lookup(id), Some(""));
274 assert_eq!(dict.lookup_id(""), Some(id));
275 }
276
277 #[test]
278 fn test_compression_integration() {
279 let dict = StringDictionary::new();
280 let serialized = dict.serialize();
281 let chunk = crate::compression::compress(akar_common::enums::CompressionType::StringDictionary, &serialized, 0);
282 let decompressed = crate::compression::decompress(&chunk, serialized.len());
283 let deserialized = StringDictionary::deserialize(&decompressed).unwrap();
284 assert!(deserialized.is_empty());
285 }
286}