1use dashmap::DashMap;
27use std::sync::Arc;
28use std::path::Path;
29use std::fs;
30use std::io;
31
32const SHARD_COUNT: usize = 256;
33
34pub struct StringIndex {
36 shards: Vec<Arc<DashMap<Vec<u8>, [u8; 32]>>>,
37 manifest_path: Option<std::path::PathBuf>,
38}
39
40impl StringIndex {
41 pub fn new() -> Self {
43 let mut shards = Vec::with_capacity(SHARD_COUNT);
44 for _ in 0..SHARD_COUNT {
45 shards.push(Arc::new(DashMap::new()));
46 }
47 Self { shards, manifest_path: None }
48 }
49
50 pub fn with_persistence(path: &Path) -> io::Result<Self> {
52 let manifest_path = path.join("string_index.json");
53 let mut idx = Self::new();
54 idx.manifest_path = Some(manifest_path.clone());
55
56 if manifest_path.exists() {
58 idx.load_from_disk()?;
59 }
60
61 Ok(idx)
62 }
63
64 fn load_from_disk(&self) -> io::Result<()> {
66 let path = self.manifest_path.as_ref().ok_or_else(|| {
67 io::Error::new(io::ErrorKind::Other, "no manifest path configured")
68 })?;
69
70 let data = fs::read_to_string(path)?;
71 let entries: Vec<(Vec<u8>, [u8; 32])> = serde_json::from_str(&data)
72 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
73
74 for (key, fixed_key) in entries {
75 self.insert(&key, fixed_key);
76 }
77
78 Ok(())
79 }
80
81 pub fn save_to_disk(&self) -> io::Result<()> {
83 let path = self.manifest_path.as_ref().ok_or_else(|| {
84 io::Error::new(io::ErrorKind::Other, "no manifest path configured")
85 })?;
86
87 use rayon::prelude::*;
89 let entries: Vec<(Vec<u8>, [u8; 32])> = self.shards
90 .par_iter()
91 .flat_map(|shard| {
92 shard.iter().map(|entry| {
93 (entry.key().clone(), *entry.value())
94 }).collect::<Vec<_>>()
95 })
96 .collect();
97
98 let json = serde_json::to_string(&entries)
99 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
100
101 let temp_path = path.with_extension("json.tmp");
103 fs::write(&temp_path, json)?;
104 fs::rename(temp_path, path)?;
105
106 Ok(())
107 }
108
109 #[inline]
111 pub fn insert(&self, string_key: &[u8], fixed_key: [u8; 32]) {
112 let shard_id = if string_key.is_empty() {
113 0
114 } else {
115 string_key[0] as usize
116 };
117 self.shards[shard_id].insert(string_key.to_vec(), fixed_key);
118 }
119
120 #[inline]
122 pub fn remove(&self, string_key: &[u8]) {
123 let shard_id = if string_key.is_empty() {
124 0
125 } else {
126 string_key[0] as usize
127 };
128 self.shards[shard_id].remove(string_key);
129 }
130
131 #[inline]
133 pub fn get(&self, string_key: &[u8]) -> Option<[u8; 32]> {
134 let shard_id = if string_key.is_empty() {
135 0
136 } else {
137 string_key[0] as usize
138 };
139 self.shards[shard_id].get(string_key).map(|entry| *entry.value())
140 }
141
142 pub fn scan_prefix(&self, prefix: &[u8]) -> Vec<(Vec<u8>, [u8; 32])> {
147 use rayon::prelude::*;
148
149 if prefix.is_empty() {
150 return self.shards
152 .par_iter()
153 .flat_map(|shard| {
154 shard.iter().map(|entry| {
155 (entry.key().clone(), *entry.value())
156 }).collect::<Vec<_>>()
157 })
158 .collect();
159 }
160
161 let first_byte = prefix[0];
164
165 let mut results: Vec<(Vec<u8>, [u8; 32])> = self.shards[first_byte as usize]
167 .iter()
168 .filter_map(|entry| {
169 let key = entry.key();
170 if key.starts_with(prefix) {
171 Some((key.clone(), *entry.value()))
172 } else {
173 None
174 }
175 })
176 .collect();
177
178 results.sort_by(|a, b| a.0.cmp(&b.0));
180 results
181 }
182
183 pub fn scan_from_reverse(&self, start: &[u8], limit: usize) -> Vec<(Vec<u8>, [u8; 32])> {
187 use rayon::prelude::*;
188
189 let mut results: Vec<(Vec<u8>, [u8; 32])> = self.shards
191 .par_iter()
192 .flat_map(|shard| {
193 shard.iter().filter_map(|entry| {
194 let key = entry.key();
195 if key.as_slice() < start {
196 Some((key.clone(), *entry.value()))
197 } else {
198 None
199 }
200 }).collect::<Vec<_>>()
201 })
202 .collect();
203
204 results.sort_by(|a, b| b.0.cmp(&a.0));
206
207 results.truncate(limit);
209 results
210 }
211
212 pub fn clear(&self) {
214 for shard in &self.shards {
215 shard.clear();
216 }
217 }
218
219 pub fn len(&self) -> usize {
221 self.shards.iter().map(|s| s.len()).sum()
222 }
223
224 pub fn is_empty(&self) -> bool {
226 self.shards.iter().all(|s| s.is_empty())
227 }
228}
229
230impl Default for StringIndex {
231 fn default() -> Self {
232 Self::new()
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn test_insert_and_get() {
242 let idx = StringIndex::new();
243 let key1 = b"batch:123";
244 let fixed1 = [1u8; 32];
245
246 idx.insert(key1, fixed1);
247 assert_eq!(idx.get(key1), Some(fixed1));
248 assert_eq!(idx.get(b"batch:456"), None);
249 }
250
251 #[test]
252 fn test_prefix_scan() {
253 let idx = StringIndex::new();
254
255 idx.insert(b"batch:1", [1u8; 32]);
256 idx.insert(b"batch:2", [2u8; 32]);
257 idx.insert(b"batch:10", [10u8; 32]);
258 idx.insert(b"tx:1", [100u8; 32]);
259
260 let results = idx.scan_prefix(b"batch:");
261 assert_eq!(results.len(), 3);
262
263 assert_eq!(results[0].0, b"batch:1");
265 assert_eq!(results[1].0, b"batch:10");
266 assert_eq!(results[2].0, b"batch:2");
267 }
268
269 #[test]
270 fn test_remove() {
271 let idx = StringIndex::new();
272 let key1 = b"batch:123";
273 let fixed1 = [1u8; 32];
274
275 idx.insert(key1, fixed1);
276 assert_eq!(idx.len(), 1);
277
278 idx.remove(key1);
279 assert_eq!(idx.len(), 0);
280 assert_eq!(idx.get(key1), None);
281 }
282
283 #[test]
284 fn test_scan_from_reverse() {
285 let idx = StringIndex::new();
286
287 idx.insert(b"key:1", [1u8; 32]);
288 idx.insert(b"key:2", [2u8; 32]);
289 idx.insert(b"key:3", [3u8; 32]);
290 idx.insert(b"key:4", [4u8; 32]);
291
292 let results = idx.scan_from_reverse(b"key:3", 10);
294 assert_eq!(results.len(), 2);
295 assert_eq!(results[0].0, b"key:2");
296 assert_eq!(results[1].0, b"key:1");
297 }
298}