leptos_sync_core/storage/
indexed.rs

1//! Indexed storage implementation for faster lookups and queries
2
3use 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/// Index-related errors
12#[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/// Index configuration
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct IndexConfig {
27    /// Index name
28    pub name: String,
29    /// Index type
30    pub index_type: IndexType,
31    /// Whether the index is unique
32    pub unique: bool,
33    /// Whether the index is sparse (allows null values)
34    pub sparse: bool,
35}
36
37/// Index types
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
39pub enum IndexType {
40    /// Hash index for exact matches
41    Hash,
42    /// B-tree index for range queries and sorting
43    BTree,
44    /// Full-text index for text search
45    FullText,
46}
47
48impl Default for IndexType {
49    fn default() -> Self {
50        Self::Hash
51    }
52}
53
54/// Index metadata
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct IndexMetadata {
57    /// Index configuration
58    pub config: IndexConfig,
59    /// Number of entries in the index
60    pub entry_count: usize,
61    /// Index size in bytes
62    pub size_bytes: usize,
63    /// Last updated timestamp
64    pub last_updated: chrono::DateTime<chrono::Utc>,
65}
66
67/// Index entry
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct IndexEntry {
70    /// Indexed value
71    pub value: String,
72    /// Associated document IDs
73    pub document_ids: Vec<String>,
74    /// Timestamp when this entry was last updated
75    pub last_updated: chrono::DateTime<chrono::Utc>,
76}
77
78/// Indexed storage implementation
79pub struct IndexedStorage {
80    /// Primary storage backend
81    primary: Arc<StorageEnum>,
82    /// Secondary indices
83    indices: Arc<RwLock<HashMap<String, Box<dyn Index>>>>,
84    /// Index metadata
85    metadata: Arc<RwLock<HashMap<String, IndexMetadata>>>,
86}
87
88/// Index trait for different index implementations
89#[async_trait::async_trait]
90pub trait Index: Send + Sync {
91    /// Get the index name
92    fn name(&self) -> &str;
93    
94    /// Get the index type
95    fn index_type(&self) -> IndexType;
96    
97    /// Insert a value with associated document ID
98    async fn insert(&mut self, value: &str, document_id: &str) -> Result<(), IndexError>;
99    
100    /// Remove a value-document association
101    async fn remove(&mut self, value: &str, document_id: &str) -> Result<(), IndexError>;
102    
103    /// Get document IDs for a value
104    async fn get(&self, value: &str) -> Result<Vec<String>, IndexError>;
105    
106    /// Get all values in the index
107    async fn values(&self) -> Result<Vec<String>, IndexError>;
108    
109    /// Get index statistics
110    async fn stats(&self) -> Result<IndexStats, IndexError>;
111    
112    /// Clear the index
113    async fn clear(&mut self) -> Result<(), IndexError>;
114}
115
116/// Index statistics
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct IndexStats {
119    /// Number of entries
120    pub entry_count: usize,
121    /// Index size in bytes
122    pub size_bytes: usize,
123    /// Average entries per value
124    pub avg_entries_per_value: f64,
125    /// Most common values
126    pub top_values: Vec<(String, usize)>,
127}
128
129/// Hash index implementation
130pub 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
223/// B-tree index implementation
224pub 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    /// Create a new indexed storage
319    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    /// Create an index
328    pub async fn create_index(&self, config: IndexConfig) -> Result<(), IndexError> {
329        let index_name = config.name.clone();
330        
331        // Check if index already exists
332        if self.indices.read().await.contains_key(&index_name) {
333            return Err(IndexError::IndexAlreadyExists(index_name));
334        }
335        
336        // Create index based on type
337        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                // TODO: Implement full-text index
342                return Err(IndexError::InvalidIndexValue);
343            }
344        };
345        
346        // Add index
347        self.indices.write().await.insert(index_name.clone(), index);
348        
349        // Add metadata
350        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    /// Drop an index
362    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    /// Get all index names
374    pub async fn list_indices(&self) -> Vec<String> {
375        self.indices.read().await.keys().cloned().collect()
376    }
377    
378    /// Get index metadata
379    pub async fn get_index_metadata(&self, name: &str) -> Option<IndexMetadata> {
380        self.metadata.read().await.get(name).cloned()
381    }
382    
383    /// Query by index
384    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    /// Range query (for B-tree indices)
393    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        // For now, return empty result
403        // TODO: Implement proper range query
404        Ok(Vec::new())
405    }
406    
407    /// Get index statistics
408    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}