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