Skip to main content

oxirs_core/storage/
mod.rs

1//! Next-Generation Storage Engine for OxiRS
2//!
3//! This module implements a quantum-ready storage architecture with:
4//! - Tiered storage with intelligent data placement
5//! - Columnar storage for analytical workloads  
6//! - Time-series optimization for temporal RDF
7//! - Immutable storage with content-addressable blocks
8//! - Advanced compression (LZ4, ZSTD, custom RDF codecs)
9//! - Storage virtualization with transparent migration
10//! - Multi-Version Concurrency Control (MVCC) for high-concurrency operations
11
12// #[cfg(feature = "columnar")]
13// pub mod columnar; // TODO: Add 'columnar' feature to Cargo.toml when ready
14pub mod compression;
15pub mod immutable;
16pub mod mmap_storage;
17pub mod mvcc;
18pub mod temporal;
19pub mod tiered;
20pub mod virtualization;
21
22pub use mvcc::{IsolationLevel, MvccConfig, MvccStore, TransactionId as MvccTransactionId};
23use parking_lot::RwLock;
24
25use crate::OxirsError;
26use std::path::Path;
27use std::sync::Arc;
28
29/// Storage configuration
30#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
31pub struct StorageConfig {
32    /// Enable tiered storage
33    pub enable_tiering: bool,
34    /// Enable columnar storage for analytics
35    pub enable_columnar: bool,
36    /// Enable temporal optimization
37    pub enable_temporal: bool,
38    /// Compression algorithm
39    pub compression: CompressionType,
40    /// Storage tiers configuration
41    pub tiers: TierConfig,
42    /// Cache size in MB
43    pub cache_size_mb: usize,
44}
45
46impl Default for StorageConfig {
47    fn default() -> Self {
48        StorageConfig {
49            enable_tiering: true,
50            enable_columnar: true,
51            enable_temporal: true,
52            compression: CompressionType::Zstd { level: 3 },
53            tiers: TierConfig::default(),
54            cache_size_mb: 1024,
55        }
56    }
57}
58
59/// Compression types
60#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
61pub enum CompressionType {
62    None,
63    Lz4 { level: u32 },
64    Zstd { level: i32 },
65    RdfCustom { dictionary_size: usize },
66}
67
68/// Storage tier configuration
69#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
70pub struct TierConfig {
71    /// Hot tier: in-memory with fastest access
72    pub hot_tier: HotTierConfig,
73    /// Warm tier: SSD-optimized storage
74    pub warm_tier: WarmTierConfig,
75    /// Cold tier: HDD/object storage
76    pub cold_tier: ColdTierConfig,
77    /// Archive tier: long-term immutable storage
78    pub archive_tier: ArchiveTierConfig,
79}
80
81/// Hot tier configuration
82#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
83pub struct HotTierConfig {
84    /// Maximum size in MB
85    pub max_size_mb: usize,
86    /// Eviction policy
87    pub eviction_policy: EvictionPolicy,
88    /// Time to live in seconds
89    pub ttl_seconds: Option<u64>,
90}
91
92impl Default for HotTierConfig {
93    fn default() -> Self {
94        HotTierConfig {
95            max_size_mb: 4096,
96            eviction_policy: EvictionPolicy::Lru,
97            ttl_seconds: Some(3600),
98        }
99    }
100}
101
102/// Warm tier configuration  
103#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
104pub struct WarmTierConfig {
105    /// Path to warm storage
106    pub path: String,
107    /// Maximum size in GB
108    pub max_size_gb: usize,
109    /// Promotion threshold (access count)
110    pub promotion_threshold: u32,
111    /// Demotion threshold (days since last access)
112    pub demotion_threshold_days: u32,
113}
114
115impl Default for WarmTierConfig {
116    fn default() -> Self {
117        WarmTierConfig {
118            path: "/var/oxirs/warm".to_string(),
119            max_size_gb: 100,
120            promotion_threshold: 10,
121            demotion_threshold_days: 7,
122        }
123    }
124}
125
126/// Cold tier configuration
127#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
128pub struct ColdTierConfig {
129    /// Path to cold storage
130    pub path: String,
131    /// Maximum size in TB
132    pub max_size_tb: usize,
133    /// Compression level
134    pub compression_level: i32,
135    /// Archive threshold (days since last access)
136    pub archive_threshold_days: u32,
137}
138
139impl Default for ColdTierConfig {
140    fn default() -> Self {
141        ColdTierConfig {
142            path: "/var/oxirs/cold".to_string(),
143            max_size_tb: 10,
144            compression_level: 9,
145            archive_threshold_days: 90,
146        }
147    }
148}
149
150/// Archive tier configuration
151#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
152pub struct ArchiveTierConfig {
153    /// Archive storage backend
154    pub backend: ArchiveBackend,
155    /// Retention policy
156    pub retention_years: Option<u32>,
157    /// Immutability guarantee
158    pub immutable: bool,
159}
160
161impl Default for ArchiveTierConfig {
162    fn default() -> Self {
163        ArchiveTierConfig {
164            backend: ArchiveBackend::Local("/var/oxirs/archive".to_string()),
165            retention_years: Some(7),
166            immutable: true,
167        }
168    }
169}
170
171/// Archive storage backend
172#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
173pub enum ArchiveBackend {
174    Local(String),
175    S3 { bucket: String, prefix: String },
176    GCS { bucket: String, prefix: String },
177    Azure { container: String, prefix: String },
178}
179
180/// Eviction policy for hot tier
181#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
182pub enum EvictionPolicy {
183    Lru,
184    Lfu,
185    Fifo,
186    Adaptive,
187}
188
189/// Storage engine trait
190#[async_trait::async_trait]
191pub trait StorageEngine: Send + Sync {
192    /// Initialize the storage engine
193    async fn init(&mut self, config: StorageConfig) -> Result<(), OxirsError>;
194
195    /// Store a triple
196    async fn store_triple(&self, triple: &crate::model::Triple) -> Result<(), OxirsError>;
197
198    /// Store multiple triples
199    async fn store_triples(&self, triples: &[crate::model::Triple]) -> Result<(), OxirsError>;
200
201    /// Query triples by pattern
202    async fn query_triples(
203        &self,
204        pattern: &crate::model::TriplePattern,
205    ) -> Result<Vec<crate::model::Triple>, OxirsError>;
206
207    /// Delete triples by pattern
208    async fn delete_triples(
209        &self,
210        pattern: &crate::model::TriplePattern,
211    ) -> Result<usize, OxirsError>;
212
213    /// Get storage statistics
214    async fn stats(&self) -> Result<StorageStats, OxirsError>;
215
216    /// Optimize storage
217    async fn optimize(&self) -> Result<(), OxirsError>;
218
219    /// Backup storage
220    async fn backup(&self, path: &Path) -> Result<(), OxirsError>;
221
222    /// Restore from backup
223    async fn restore(&self, path: &Path) -> Result<(), OxirsError>;
224}
225
226/// Storage statistics
227#[derive(Debug, Clone)]
228pub struct StorageStats {
229    /// Total number of triples
230    pub total_triples: u64,
231    /// Storage size in bytes
232    pub total_size_bytes: u64,
233    /// Tier distribution
234    pub tier_stats: TierStats,
235    /// Compression ratio
236    pub compression_ratio: f64,
237    /// Query performance metrics
238    pub query_metrics: QueryMetrics,
239}
240
241/// Tier statistics
242#[derive(Debug, Clone)]
243pub struct TierStats {
244    /// Hot tier stats
245    pub hot: TierStat,
246    /// Warm tier stats
247    pub warm: TierStat,
248    /// Cold tier stats
249    pub cold: TierStat,
250    /// Archive tier stats
251    pub archive: TierStat,
252}
253
254/// Individual tier statistics
255#[derive(Debug, Clone)]
256pub struct TierStat {
257    /// Number of triples
258    pub triple_count: u64,
259    /// Size in bytes
260    pub size_bytes: u64,
261    /// Hit rate percentage
262    pub hit_rate: f64,
263    /// Average access time in microseconds
264    pub avg_access_time_us: u64,
265}
266
267/// Query performance metrics
268#[derive(Debug, Clone)]
269pub struct QueryMetrics {
270    /// Average query time in milliseconds
271    pub avg_query_time_ms: f64,
272    /// 99th percentile query time
273    pub p99_query_time_ms: f64,
274    /// Queries per second
275    pub qps: f64,
276    /// Cache hit rate
277    pub cache_hit_rate: f64,
278}
279
280/// Create a new storage engine with the given configuration
281pub async fn create_engine(config: StorageConfig) -> Result<Arc<dyn StorageEngine>, OxirsError> {
282    let engine = SimpleStorageEngine::new(config).await?;
283    Ok(Arc::new(engine))
284}
285
286/// Simple file-based storage engine implementation
287pub struct SimpleStorageEngine {
288    #[allow(dead_code)]
289    config: StorageConfig,
290    mvcc_store: MvccStore,
291    stats: Arc<RwLock<StorageStats>>,
292    #[allow(dead_code)]
293    base_path: std::path::PathBuf,
294}
295
296impl SimpleStorageEngine {
297    /// Create a new simple storage engine
298    pub async fn new(config: StorageConfig) -> Result<Self, OxirsError> {
299        let base_path = std::path::PathBuf::from("/tmp/oxirs_storage");
300        std::fs::create_dir_all(&base_path)
301            .map_err(|e| OxirsError::Store(format!("Failed to create storage directory: {e}")))?;
302
303        let mvcc_config = MvccConfig {
304            max_versions_per_triple: 100,
305            gc_interval: std::time::Duration::from_secs(60),
306            min_version_age: std::time::Duration::from_secs(30),
307            enable_snapshot_isolation: true,
308            enable_read_your_writes: true,
309            conflict_detection: mvcc::ConflictDetection::OptimisticTwoPhase,
310        };
311
312        let mvcc_store = MvccStore::new(mvcc_config);
313
314        let initial_stats = StorageStats {
315            total_triples: 0,
316            total_size_bytes: 0,
317            tier_stats: TierStats {
318                hot: TierStat {
319                    triple_count: 0,
320                    size_bytes: 0,
321                    hit_rate: 0.0,
322                    avg_access_time_us: 0,
323                },
324                warm: TierStat {
325                    triple_count: 0,
326                    size_bytes: 0,
327                    hit_rate: 0.0,
328                    avg_access_time_us: 0,
329                },
330                cold: TierStat {
331                    triple_count: 0,
332                    size_bytes: 0,
333                    hit_rate: 0.0,
334                    avg_access_time_us: 0,
335                },
336                archive: TierStat {
337                    triple_count: 0,
338                    size_bytes: 0,
339                    hit_rate: 0.0,
340                    avg_access_time_us: 0,
341                },
342            },
343            compression_ratio: 1.0,
344            query_metrics: QueryMetrics {
345                avg_query_time_ms: 0.0,
346                p99_query_time_ms: 0.0,
347                qps: 0.0,
348                cache_hit_rate: 0.0,
349            },
350        };
351
352        Ok(Self {
353            config,
354            mvcc_store,
355            stats: Arc::new(RwLock::new(initial_stats)),
356            base_path,
357        })
358    }
359}
360
361#[async_trait::async_trait]
362impl StorageEngine for SimpleStorageEngine {
363    async fn init(&mut self, _config: StorageConfig) -> Result<(), OxirsError> {
364        // Already initialized in new()
365        Ok(())
366    }
367
368    async fn store_triple(&self, triple: &crate::model::Triple) -> Result<(), OxirsError> {
369        let tx_id = self
370            .mvcc_store
371            .begin_transaction(IsolationLevel::Snapshot)
372            .map_err(|e| OxirsError::Store(format!("Failed to begin transaction: {e}")))?;
373
374        self.mvcc_store
375            .insert(tx_id, triple.clone())
376            .map_err(|e| OxirsError::Store(format!("Failed to insert triple: {e}")))?;
377
378        self.mvcc_store
379            .commit_transaction(tx_id)
380            .map_err(|e| OxirsError::Store(format!("Failed to commit transaction: {e}")))?;
381
382        // Update statistics
383        let mut stats = self.stats.write();
384        stats.total_triples += 1;
385        stats.tier_stats.hot.triple_count += 1;
386
387        Ok(())
388    }
389
390    async fn store_triples(&self, triples: &[crate::model::Triple]) -> Result<(), OxirsError> {
391        let tx_id = self
392            .mvcc_store
393            .begin_transaction(IsolationLevel::Snapshot)
394            .map_err(|e| OxirsError::Store(format!("Failed to begin transaction: {e}")))?;
395
396        for triple in triples {
397            self.mvcc_store
398                .insert(tx_id, triple.clone())
399                .map_err(|e| OxirsError::Store(format!("Failed to insert triple: {e}")))?;
400        }
401
402        self.mvcc_store
403            .commit_transaction(tx_id)
404            .map_err(|e| OxirsError::Store(format!("Failed to commit transaction: {e}")))?;
405
406        // Update statistics
407        let mut stats = self.stats.write();
408        stats.total_triples += triples.len() as u64;
409        stats.tier_stats.hot.triple_count += triples.len() as u64;
410
411        Ok(())
412    }
413
414    async fn query_triples(
415        &self,
416        pattern: &crate::model::TriplePattern,
417    ) -> Result<Vec<crate::model::Triple>, OxirsError> {
418        let tx_id = self
419            .mvcc_store
420            .begin_transaction(IsolationLevel::Snapshot)
421            .map_err(|e| OxirsError::Store(format!("Failed to begin transaction: {e}")))?;
422
423        // Convert patterns to concrete terms for MVCC query
424        let subject = Self::pattern_to_subject(pattern.subject());
425        let predicate = Self::pattern_to_predicate(pattern.predicate());
426        let object = Self::pattern_to_object(pattern.object());
427
428        let results = self
429            .mvcc_store
430            .query(tx_id, subject.as_ref(), predicate.as_ref(), object.as_ref())
431            .map_err(|e| OxirsError::Store(format!("Failed to query triples: {e}")))?;
432
433        // Filter results to match the pattern (in case of variables)
434        let filtered: Vec<_> = results
435            .into_iter()
436            .filter(|triple| pattern.matches(triple))
437            .collect();
438
439        Ok(filtered)
440    }
441
442    async fn delete_triples(
443        &self,
444        pattern: &crate::model::TriplePattern,
445    ) -> Result<usize, OxirsError> {
446        let tx_id = self
447            .mvcc_store
448            .begin_transaction(IsolationLevel::Snapshot)
449            .map_err(|e| OxirsError::Store(format!("Failed to begin transaction: {e}")))?;
450
451        // Convert patterns to concrete terms for MVCC query
452        let subject = Self::pattern_to_subject(pattern.subject());
453        let predicate = Self::pattern_to_predicate(pattern.predicate());
454        let object = Self::pattern_to_object(pattern.object());
455
456        // First query to find matching triples
457        let matching_triples = self
458            .mvcc_store
459            .query(tx_id, subject.as_ref(), predicate.as_ref(), object.as_ref())
460            .map_err(|e| OxirsError::Store(format!("Failed to query triples for deletion: {e}")))?;
461
462        // Filter results to match the pattern exactly
463        let filtered: Vec<_> = matching_triples
464            .into_iter()
465            .filter(|triple| pattern.matches(triple))
466            .collect();
467
468        let deleted_count = filtered.len();
469
470        // Delete each matching triple
471        for triple in &filtered {
472            self.mvcc_store
473                .delete(tx_id, triple)
474                .map_err(|e| OxirsError::Store(format!("Failed to delete triple: {e}")))?;
475        }
476
477        self.mvcc_store
478            .commit_transaction(tx_id)
479            .map_err(|e| OxirsError::Store(format!("Failed to commit transaction: {e}")))?;
480
481        // Update statistics
482        let mut stats = self.stats.write();
483        stats.total_triples = stats.total_triples.saturating_sub(deleted_count as u64);
484        stats.tier_stats.hot.triple_count = stats
485            .tier_stats
486            .hot
487            .triple_count
488            .saturating_sub(deleted_count as u64);
489
490        Ok(deleted_count)
491    }
492
493    async fn stats(&self) -> Result<StorageStats, OxirsError> {
494        let stats = self.stats.read();
495        Ok(stats.clone())
496    }
497
498    async fn optimize(&self) -> Result<(), OxirsError> {
499        // Run garbage collection on MVCC store
500        self.mvcc_store
501            .garbage_collect()
502            .map_err(|e| OxirsError::Store(format!("Failed to optimize storage: {e}")))?;
503        Ok(())
504    }
505
506    async fn backup(&self, path: &Path) -> Result<(), OxirsError> {
507        // Simple backup implementation - serialize current state to file
508        let backup_path = path.join("oxirs_backup.json");
509
510        // Get all triples using a query that matches everything
511        let all_pattern = crate::model::TriplePattern::new(None, None, None);
512        let triples = self.query_triples(&all_pattern).await?;
513
514        let serialized = serde_json::to_string_pretty(&triples)
515            .map_err(|e| OxirsError::Store(format!("Failed to serialize backup: {e}")))?;
516
517        std::fs::write(&backup_path, serialized)
518            .map_err(|e| OxirsError::Store(format!("Failed to write backup: {e}")))?;
519
520        Ok(())
521    }
522
523    async fn restore(&self, path: &Path) -> Result<(), OxirsError> {
524        // Simple restore implementation - deserialize from file
525        let backup_path = path.join("oxirs_backup.json");
526
527        let serialized = std::fs::read_to_string(&backup_path)
528            .map_err(|e| OxirsError::Store(format!("Failed to read backup: {e}")))?;
529
530        let triples: Vec<crate::model::Triple> = serde_json::from_str(&serialized)
531            .map_err(|e| OxirsError::Store(format!("Failed to deserialize backup: {e}")))?;
532
533        self.store_triples(&triples).await?;
534
535        Ok(())
536    }
537}
538
539impl SimpleStorageEngine {
540    /// Convert a subject pattern to a concrete subject term
541    fn pattern_to_subject(
542        pattern: Option<&crate::model::pattern::SubjectPattern>,
543    ) -> Option<crate::model::Subject> {
544        pattern?.try_into().ok()
545    }
546
547    /// Convert a predicate pattern to a concrete predicate term
548    fn pattern_to_predicate(
549        pattern: Option<&crate::model::pattern::PredicatePattern>,
550    ) -> Option<crate::model::Predicate> {
551        pattern?.try_into().ok()
552    }
553
554    /// Convert an object pattern to a concrete object term
555    fn pattern_to_object(
556        pattern: Option<&crate::model::pattern::ObjectPattern>,
557    ) -> Option<crate::model::Object> {
558        pattern?.try_into().ok()
559    }
560}
561
562#[cfg(test)]
563mod tests {
564    use super::*;
565
566    #[test]
567    fn test_default_config() {
568        let config = StorageConfig::default();
569        assert!(config.enable_tiering);
570        assert!(config.enable_columnar);
571        assert!(config.enable_temporal);
572        assert_eq!(config.cache_size_mb, 1024);
573    }
574}