leptos_sync_core/storage/
indexed.rs1use crate::storage::StorageError;
4use crate::storage::Storage as StorageEnum;
5use serde::{Deserialize, Serialize};
6use std::collections::{BTreeMap, HashMap};
7use std::sync::Arc;
8use tokio::sync::RwLock;
9use thiserror::Error;
10
11#[derive(Error, Debug)]
13pub enum IndexError {
14 #[error("Storage error: {0}")]
15 Storage(#[from] StorageError),
16 #[error("Index not found: {0}")]
17 IndexNotFound(String),
18 #[error("Index already exists: {0}")]
19 IndexAlreadyExists(String),
20 #[error("Invalid index value")]
21 InvalidIndexValue,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct IndexConfig {
27 pub name: String,
29 pub index_type: IndexType,
31 pub unique: bool,
33 pub sparse: bool,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
39pub enum IndexType {
40 Hash,
42 BTree,
44 FullText,
46}
47
48impl Default for IndexType {
49 fn default() -> Self {
50 Self::Hash
51 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct IndexMetadata {
57 pub config: IndexConfig,
59 pub entry_count: usize,
61 pub size_bytes: usize,
63 pub last_updated: chrono::DateTime<chrono::Utc>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct IndexEntry {
70 pub value: String,
72 pub document_ids: Vec<String>,
74 pub last_updated: chrono::DateTime<chrono::Utc>,
76}
77
78pub struct IndexedStorage {
80 primary: Arc<StorageEnum>,
82 indices: Arc<RwLock<HashMap<String, Box<dyn Index>>>>,
84 metadata: Arc<RwLock<HashMap<String, IndexMetadata>>>,
86}
87
88#[async_trait::async_trait]
90pub trait Index: Send + Sync {
91 fn name(&self) -> &str;
93
94 fn index_type(&self) -> IndexType;
96
97 async fn insert(&mut self, value: &str, document_id: &str) -> Result<(), IndexError>;
99
100 async fn remove(&mut self, value: &str, document_id: &str) -> Result<(), IndexError>;
102
103 async fn get(&self, value: &str) -> Result<Vec<String>, IndexError>;
105
106 async fn values(&self) -> Result<Vec<String>, IndexError>;
108
109 async fn stats(&self) -> Result<IndexStats, IndexError>;
111
112 async fn clear(&mut self) -> Result<(), IndexError>;
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct IndexStats {
119 pub entry_count: usize,
121 pub size_bytes: usize,
123 pub avg_entries_per_value: f64,
125 pub top_values: Vec<(String, usize)>,
127}
128
129pub struct HashIndex {
131 name: String,
132 data: HashMap<String, Vec<String>>,
133 unique: bool,
134 sparse: bool,
135}
136
137impl HashIndex {
138 pub fn new(name: String, config: &IndexConfig) -> Self {
139 Self {
140 name,
141 data: HashMap::new(),
142 unique: config.unique,
143 sparse: config.sparse,
144 }
145 }
146}
147
148#[async_trait::async_trait]
149impl Index for HashIndex {
150 fn name(&self) -> &str {
151 &self.name
152 }
153
154 fn index_type(&self) -> IndexType {
155 IndexType::Hash
156 }
157
158 async fn insert(&mut self, value: &str, document_id: &str) -> Result<(), IndexError> {
159 if value.is_empty() && !self.sparse {
160 return Err(IndexError::InvalidIndexValue);
161 }
162
163 let entry = self.data.entry(value.to_string()).or_insert_with(Vec::new);
164
165 if self.unique && !entry.is_empty() {
166 return Err(IndexError::InvalidIndexValue);
167 }
168
169 if !entry.contains(&document_id.to_string()) {
170 entry.push(document_id.to_string());
171 }
172
173 Ok(())
174 }
175
176 async fn remove(&mut self, value: &str, document_id: &str) -> Result<(), IndexError> {
177 if let Some(entry) = self.data.get_mut(value) {
178 entry.retain(|id| id != document_id);
179 if entry.is_empty() {
180 self.data.remove(value);
181 }
182 }
183 Ok(())
184 }
185
186 async fn get(&self, value: &str) -> Result<Vec<String>, IndexError> {
187 Ok(self.data.get(value).cloned().unwrap_or_default())
188 }
189
190 async fn values(&self) -> Result<Vec<String>, IndexError> {
191 Ok(self.data.keys().cloned().collect())
192 }
193
194 async fn stats(&self) -> Result<IndexStats, IndexError> {
195 let entry_count = self.data.len();
196 let total_docs: usize = self.data.values().map(|v| v.len()).sum();
197 let avg_entries = if entry_count > 0 {
198 total_docs as f64 / entry_count as f64
199 } else {
200 0.0
201 };
202
203 let mut top_values: Vec<_> = self.data.iter()
204 .map(|(k, v)| (k.clone(), v.len()))
205 .collect();
206 top_values.sort_by(|a, b| b.1.cmp(&a.1));
207 top_values.truncate(10);
208
209 Ok(IndexStats {
210 entry_count,
211 size_bytes: std::mem::size_of_val(&self.data),
212 avg_entries_per_value: avg_entries,
213 top_values,
214 })
215 }
216
217 async fn clear(&mut self) -> Result<(), IndexError> {
218 self.data.clear();
219 Ok(())
220 }
221}
222
223pub struct BTreeIndex {
225 name: String,
226 data: BTreeMap<String, Vec<String>>,
227 unique: bool,
228 sparse: bool,
229}
230
231impl BTreeIndex {
232 pub fn new(name: String, config: &IndexConfig) -> Self {
233 Self {
234 name,
235 data: BTreeMap::new(),
236 unique: config.unique,
237 sparse: config.sparse,
238 }
239 }
240}
241
242#[async_trait::async_trait]
243impl Index for BTreeIndex {
244 fn name(&self) -> &str {
245 &self.name
246 }
247
248 fn index_type(&self) -> IndexType {
249 IndexType::BTree
250 }
251
252 async fn insert(&mut self, value: &str, document_id: &str) -> Result<(), IndexError> {
253 if value.is_empty() && !self.sparse {
254 return Err(IndexError::InvalidIndexValue);
255 }
256
257 let entry = self.data.entry(value.to_string()).or_insert_with(Vec::new);
258
259 if self.unique && !entry.is_empty() {
260 return Err(IndexError::InvalidIndexValue);
261 }
262
263 if !entry.contains(&document_id.to_string()) {
264 entry.push(document_id.to_string());
265 }
266
267 Ok(())
268 }
269
270 async fn remove(&mut self, value: &str, document_id: &str) -> Result<(), IndexError> {
271 if let Some(entry) = self.data.get_mut(value) {
272 entry.retain(|id| id != document_id);
273 if entry.is_empty() {
274 self.data.remove(value);
275 }
276 }
277 Ok(())
278 }
279
280 async fn get(&self, value: &str) -> Result<Vec<String>, IndexError> {
281 Ok(self.data.get(value).cloned().unwrap_or_default())
282 }
283
284 async fn values(&self) -> Result<Vec<String>, IndexError> {
285 Ok(self.data.keys().cloned().collect())
286 }
287
288 async fn stats(&self) -> Result<IndexStats, IndexError> {
289 let entry_count = self.data.len();
290 let total_docs: usize = self.data.values().map(|v| v.len()).sum();
291 let avg_entries = if entry_count > 0 {
292 total_docs as f64 / entry_count as f64
293 } else {
294 0.0
295 };
296
297 let mut top_values: Vec<_> = self.data.iter()
298 .map(|(k, v)| (k.clone(), v.len()))
299 .collect();
300 top_values.sort_by(|a, b| b.1.cmp(&a.1));
301 top_values.truncate(10);
302
303 Ok(IndexStats {
304 entry_count,
305 size_bytes: std::mem::size_of_val(&self.data),
306 avg_entries_per_value: avg_entries,
307 top_values,
308 })
309 }
310
311 async fn clear(&mut self) -> Result<(), IndexError> {
312 self.data.clear();
313 Ok(())
314 }
315}
316
317impl IndexedStorage {
318 pub fn new(primary: Arc<StorageEnum>) -> Self {
320 Self {
321 primary,
322 indices: Arc::new(RwLock::new(HashMap::new())),
323 metadata: Arc::new(RwLock::new(HashMap::new())),
324 }
325 }
326
327 pub async fn create_index(&self, config: IndexConfig) -> Result<(), IndexError> {
329 let index_name = config.name.clone();
330
331 if self.indices.read().await.contains_key(&index_name) {
333 return Err(IndexError::IndexAlreadyExists(index_name));
334 }
335
336 let index: Box<dyn Index> = match config.index_type {
338 IndexType::Hash => Box::new(HashIndex::new(index_name.clone(), &config)),
339 IndexType::BTree => Box::new(BTreeIndex::new(index_name.clone(), &config)),
340 IndexType::FullText => {
341 return Err(IndexError::InvalidIndexValue);
343 }
344 };
345
346 self.indices.write().await.insert(index_name.clone(), index);
348
349 let metadata = IndexMetadata {
351 config,
352 entry_count: 0,
353 size_bytes: 0,
354 last_updated: chrono::Utc::now(),
355 };
356 self.metadata.write().await.insert(index_name, metadata);
357
358 Ok(())
359 }
360
361 pub async fn drop_index(&self, name: &str) -> Result<(), IndexError> {
363 if !self.indices.read().await.contains_key(name) {
364 return Err(IndexError::IndexNotFound(name.to_string()));
365 }
366
367 self.indices.write().await.remove(name);
368 self.metadata.write().await.remove(name);
369
370 Ok(())
371 }
372
373 pub async fn list_indices(&self) -> Vec<String> {
375 self.indices.read().await.keys().cloned().collect()
376 }
377
378 pub async fn get_index_metadata(&self, name: &str) -> Option<IndexMetadata> {
380 self.metadata.read().await.get(name).cloned()
381 }
382
383 pub async fn query_by_index(&self, index_name: &str, value: &str) -> Result<Vec<String>, IndexError> {
385 let indices = self.indices.read().await;
386 let index = indices.get(index_name)
387 .ok_or_else(|| IndexError::IndexNotFound(index_name.to_string()))?;
388
389 index.get(value).await
390 }
391
392 pub async fn range_query(&self, index_name: &str, _start: &str, _end: &str) -> Result<Vec<String>, IndexError> {
394 let indices = self.indices.read().await;
395 let index = indices.get(index_name)
396 .ok_or_else(|| IndexError::IndexNotFound(index_name.to_string()))?;
397
398 if index.index_type() != IndexType::BTree {
399 return Err(IndexError::InvalidIndexValue);
400 }
401
402 Ok(Vec::new())
405 }
406
407 pub async fn get_index_stats(&self, name: &str) -> Result<IndexStats, IndexError> {
409 let indices = self.indices.read().await;
410 let index = indices.get(name)
411 .ok_or_else(|| IndexError::IndexNotFound(name.to_string()))?;
412
413 index.stats().await
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420 use crate::storage::memory::MemoryStorage;
421
422 #[tokio::test]
423 async fn test_index_creation() {
424 let primary = Arc::new(StorageEnum::Memory(MemoryStorage::new()));
425 let indexed = IndexedStorage::new(primary);
426
427 let config = IndexConfig {
428 name: "test_index".to_string(),
429 index_type: IndexType::Hash,
430 unique: false,
431 sparse: false,
432 };
433
434 assert!(indexed.create_index(config).await.is_ok());
435 assert!(indexed.list_indices().await.contains(&"test_index".to_string()));
436 }
437
438 #[tokio::test]
439 async fn test_duplicate_index() {
440 let primary = Arc::new(StorageEnum::Memory(MemoryStorage::new()));
441 let indexed = IndexedStorage::new(primary);
442
443 let config = IndexConfig {
444 name: "test_index".to_string(),
445 index_type: IndexType::Hash,
446 unique: false,
447 sparse: false,
448 };
449
450 assert!(indexed.create_index(config.clone()).await.is_ok());
451 assert!(indexed.create_index(config).await.is_err());
452 }
453
454 #[tokio::test]
455 async fn test_index_drop() {
456 let primary = Arc::new(StorageEnum::Memory(MemoryStorage::new()));
457 let indexed = IndexedStorage::new(primary);
458
459 let config = IndexConfig {
460 name: "test_index".to_string(),
461 index_type: IndexType::Hash,
462 unique: false,
463 sparse: false,
464 };
465
466 assert!(indexed.create_index(config).await.is_ok());
467 assert!(indexed.drop_index("test_index").await.is_ok());
468 assert!(!indexed.list_indices().await.contains(&"test_index".to_string()));
469 }
470}