oxirs_tdb/compression/
prefix.rs1use crate::error::{Result, TdbError};
2use oxicode::Decode;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
11pub struct PrefixCompressor {
12 prefixes: HashMap<u32, String>,
14 prefix_to_id: HashMap<String, u32>,
16 next_prefix_id: u32,
18 min_prefix_len: usize,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct CompressedString {
25 pub prefix_id: u32,
27 pub suffix: String,
29}
30
31impl PrefixCompressor {
32 pub fn new(min_prefix_len: usize) -> Self {
34 Self {
35 prefixes: HashMap::new(),
36 prefix_to_id: HashMap::new(),
37 next_prefix_id: 1,
38 min_prefix_len,
39 }
40 }
41
42 pub fn compress(&mut self, s: &str) -> Result<CompressedString> {
44 let mut best_prefix_id = 0;
46 let mut best_prefix_len = 0;
47
48 for (prefix, &prefix_id) in &self.prefix_to_id {
49 if s.starts_with(prefix) && prefix.len() > best_prefix_len {
50 best_prefix_id = prefix_id;
51 best_prefix_len = prefix.len();
52 }
53 }
54
55 if best_prefix_len > 0 {
56 Ok(CompressedString {
58 prefix_id: best_prefix_id,
59 suffix: s[best_prefix_len..].to_string(),
60 })
61 } else if s.len() >= self.min_prefix_len {
62 if let Some(prefix_end) = self.find_prefix_boundary(s) {
64 let prefix = &s[..prefix_end];
65 let prefix_id = self.add_prefix(prefix);
66
67 Ok(CompressedString {
68 prefix_id,
69 suffix: s[prefix_end..].to_string(),
70 })
71 } else {
72 Ok(CompressedString {
74 prefix_id: 0,
75 suffix: s.to_string(),
76 })
77 }
78 } else {
79 Ok(CompressedString {
81 prefix_id: 0,
82 suffix: s.to_string(),
83 })
84 }
85 }
86
87 pub fn decompress(&self, compressed: &CompressedString) -> Result<String> {
89 if compressed.prefix_id == 0 {
90 Ok(compressed.suffix.clone())
91 } else {
92 let prefix = self.prefixes.get(&compressed.prefix_id).ok_or_else(|| {
93 TdbError::Other(format!("Prefix {} not found", compressed.prefix_id))
94 })?;
95
96 Ok(format!("{}{}", prefix, compressed.suffix))
97 }
98 }
99
100 fn add_prefix(&mut self, prefix: &str) -> u32 {
102 if let Some(&id) = self.prefix_to_id.get(prefix) {
103 return id;
104 }
105
106 let id = self.next_prefix_id;
107 self.next_prefix_id += 1;
108
109 self.prefixes.insert(id, prefix.to_string());
110 self.prefix_to_id.insert(prefix.to_string(), id);
111
112 id
113 }
114
115 fn find_prefix_boundary(&self, s: &str) -> Option<usize> {
117 if let Some(pos) = s.rfind('#') {
119 return Some(pos + 1);
120 }
121
122 if let Some(pos) = s.rfind('/') {
123 if pos + 1 < s.len() {
124 return Some(pos + 1);
125 }
126 }
127
128 None
129 }
130
131 pub fn stats(&self) -> CompressionStats {
133 CompressionStats {
134 num_prefixes: self.prefixes.len(),
135 total_prefix_bytes: self.prefixes.values().map(|p| p.len()).sum(),
136 }
137 }
138
139 pub fn num_prefixes(&self) -> usize {
141 self.prefixes.len()
142 }
143
144 pub fn get_prefix(&self, prefix_id: u32) -> Option<&String> {
146 self.prefixes.get(&prefix_id)
147 }
148}
149
150impl Default for PrefixCompressor {
151 fn default() -> Self {
152 Self::new(10) }
154}
155
156#[derive(Debug, Clone, Copy)]
158pub struct CompressionStats {
159 pub num_prefixes: usize,
161 pub total_prefix_bytes: usize,
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_prefix_compression_basic() {
171 let mut compressor = PrefixCompressor::new(10);
172
173 let iri1 = "http://example.org/Person";
174 let iri2 = "http://example.org/name";
175
176 let c1 = compressor.compress(iri1).unwrap();
177 let c2 = compressor.compress(iri2).unwrap();
178
179 assert_eq!(c1.prefix_id, c2.prefix_id);
181 assert!(c1.prefix_id > 0);
182
183 assert_eq!(c1.suffix, "Person");
184 assert_eq!(c2.suffix, "name");
185
186 let d1 = compressor.decompress(&c1).unwrap();
187 let d2 = compressor.decompress(&c2).unwrap();
188
189 assert_eq!(d1, iri1);
190 assert_eq!(d2, iri2);
191 }
192
193 #[test]
194 fn test_prefix_compression_no_prefix() {
195 let mut compressor = PrefixCompressor::new(10);
196
197 let short_str = "abc";
198 let c = compressor.compress(short_str).unwrap();
199
200 assert_eq!(c.prefix_id, 0);
201 assert_eq!(c.suffix, "abc");
202
203 let d = compressor.decompress(&c).unwrap();
204 assert_eq!(d, short_str);
205 }
206
207 #[test]
208 fn test_prefix_compression_hash_iri() {
209 let mut compressor = PrefixCompressor::new(10);
210
211 let iri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
212 let c = compressor.compress(iri).unwrap();
213
214 assert!(c.prefix_id > 0);
215 assert_eq!(c.suffix, "type");
216
217 let d = compressor.decompress(&c).unwrap();
218 assert_eq!(d, iri);
219 }
220
221 #[test]
222 fn test_prefix_compression_multiple_prefixes() {
223 let mut compressor = PrefixCompressor::new(10);
224
225 let iri1 = "http://example.org/Person";
226 let iri2 = "http://xmlns.com/foaf/0.1/name";
227
228 let c1 = compressor.compress(iri1).unwrap();
229 let c2 = compressor.compress(iri2).unwrap();
230
231 assert_ne!(c1.prefix_id, c2.prefix_id);
233 assert!(c1.prefix_id > 0);
234 assert!(c2.prefix_id > 0);
235
236 assert_eq!(compressor.num_prefixes(), 2);
237 }
238
239 #[test]
240 fn test_prefix_compression_stats() {
241 let mut compressor = PrefixCompressor::new(10);
242
243 compressor.compress("http://example.org/Person").unwrap();
244 compressor.compress("http://example.org/name").unwrap();
245
246 let stats = compressor.stats();
247 assert_eq!(stats.num_prefixes, 1);
248 assert!(stats.total_prefix_bytes > 0);
249 }
250
251 #[test]
252 fn test_prefix_reuse() {
253 let mut compressor = PrefixCompressor::new(10);
254
255 let iri1 = "http://example.org/Person";
256 let iri2 = "http://example.org/name";
257 let iri3 = "http://example.org/age";
258
259 let c1 = compressor.compress(iri1).unwrap();
260 let c2 = compressor.compress(iri2).unwrap();
261 let c3 = compressor.compress(iri3).unwrap();
262
263 assert_eq!(c1.prefix_id, c2.prefix_id);
264 assert_eq!(c2.prefix_id, c3.prefix_id);
265 assert_eq!(compressor.num_prefixes(), 1);
266 }
267
268 #[test]
269 fn test_prefix_get() {
270 let mut compressor = PrefixCompressor::new(10);
271
272 let iri = "http://example.org/Person";
273 let c = compressor.compress(iri).unwrap();
274
275 let prefix = compressor.get_prefix(c.prefix_id).unwrap();
276 assert_eq!(prefix, "http://example.org/");
277 }
278
279 #[test]
280 fn test_compression_serialization() {
281 let compressed = CompressedString {
282 prefix_id: 1,
283 suffix: "Person".to_string(),
284 };
285
286 let serialized =
287 oxicode::serde::encode_to_vec(&compressed, oxicode::config::standard()).unwrap();
288 let deserialized: CompressedString =
289 oxicode::serde::decode_from_slice(&serialized, oxicode::config::standard())
290 .unwrap()
291 .0;
292
293 assert_eq!(compressed, deserialized);
294 }
295}