Skip to main content

oxirs_core/storage/
virtualization.rs

1//! Storage virtualization with transparent migration
2//!
3//! This module provides a virtualized storage layer that can transparently
4//! route requests to different storage backends and migrate data between them.
5
6use crate::model::{Triple, TriplePattern};
7use crate::storage::StorageEngine;
8// Note: tiered::TieredStorageEngine temporarily disabled due to dependency conflicts
9use crate::OxirsError;
10use async_trait::async_trait;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14use std::sync::Arc;
15use tokio::sync::RwLock;
16
17/// Virtual storage configuration
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct VirtualStorageConfig {
20    /// Base path for virtual storage metadata
21    pub path: PathBuf,
22    /// Backend configurations
23    pub backends: Vec<BackendConfig>,
24    /// Routing policy
25    pub routing: RoutingPolicy,
26    /// Migration policy
27    pub migration: MigrationPolicy,
28    /// Enable transparent caching
29    pub caching: bool,
30    /// Cache size in MB
31    pub cache_size_mb: usize,
32}
33
34impl Default for VirtualStorageConfig {
35    fn default() -> Self {
36        VirtualStorageConfig {
37            path: PathBuf::from("/var/oxirs/virtual"),
38            backends: vec![BackendConfig {
39                name: "primary".to_string(),
40                backend_type: BackendType::Tiered,
41                config: serde_json::json!({
42                    "enable_tiering": true,
43                    "enable_columnar": false,
44                    "enable_temporal": false,
45                    "compression": {
46                        "Zstd": { "level": 3 }
47                    },
48                    "tiers": {
49                        "hot_tier": {
50                            "max_size_mb": 1024,
51                            "eviction_policy": "Lru",
52                            "ttl_seconds": 3600
53                        },
54                        "warm_tier": {
55                            "path": "/tmp/oxirs_virtual_warm",
56                            "max_size_gb": 10,
57                            "promotion_threshold": 10,
58                            "demotion_threshold_days": 7
59                        },
60                        "cold_tier": {
61                            "path": "/tmp/oxirs_virtual_cold",
62                            "max_size_tb": 1,
63                            "compression_level": 9,
64                            "archive_threshold_days": 90
65                        },
66                        "archive_tier": {
67                            "backend": { "Local": "/tmp/oxirs_virtual_archive" },
68                            "retention_years": 7,
69                            "immutable": true
70                        }
71                    },
72                    "cache_size_mb": 512
73                }),
74                weight: 1.0,
75                read_only: false,
76            }],
77            routing: RoutingPolicy::default(),
78            migration: MigrationPolicy::default(),
79            caching: true,
80            cache_size_mb: 1024,
81        }
82    }
83}
84
85/// Backend configuration
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct BackendConfig {
88    /// Backend name
89    pub name: String,
90    /// Backend type
91    pub backend_type: BackendType,
92    /// Backend-specific configuration
93    pub config: serde_json::Value,
94    /// Routing weight (for load balancing)
95    pub weight: f64,
96    /// Read-only flag
97    pub read_only: bool,
98}
99
100/// Storage backend type
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub enum BackendType {
103    Tiered,
104    Columnar,
105    Immutable,
106    Temporal,
107    Remote { endpoint: String },
108    Cloud { provider: CloudProvider },
109}
110
111/// Cloud storage provider
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub enum CloudProvider {
114    AWS { bucket: String, region: String },
115    GCP { bucket: String, project: String },
116    Azure { container: String, account: String },
117}
118
119/// Routing policy for virtual storage
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct RoutingPolicy {
122    /// Read routing strategy
123    pub read_strategy: ReadStrategy,
124    /// Write routing strategy
125    pub write_strategy: WriteStrategy,
126    /// Query routing hints
127    pub query_hints: HashMap<String, String>,
128    /// Predicate-based routing rules
129    pub predicate_rules: HashMap<String, String>,
130}
131
132impl Default for RoutingPolicy {
133    fn default() -> Self {
134        RoutingPolicy {
135            read_strategy: ReadStrategy::FirstAvailable,
136            write_strategy: WriteStrategy::All,
137            query_hints: HashMap::new(),
138            predicate_rules: HashMap::new(),
139        }
140    }
141}
142
143/// Read routing strategy
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub enum ReadStrategy {
146    /// Use first available backend
147    FirstAvailable,
148    /// Round-robin across backends
149    RoundRobin,
150    /// Weighted random selection
151    WeightedRandom,
152    /// Query all and merge results
153    Broadcast,
154    /// Use specific backend based on pattern
155    PatternBased,
156}
157
158/// Write routing strategy
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub enum WriteStrategy {
161    /// Write to all backends
162    All,
163    /// Write to primary only
164    PrimaryOnly,
165    /// Write to N backends
166    Quorum { n: usize },
167    /// Write based on data characteristics
168    Selective,
169}
170
171/// Migration policy
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct MigrationPolicy {
174    /// Enable automatic migration
175    pub auto_migrate: bool,
176    /// Migration trigger
177    pub trigger: MigrationTrigger,
178    /// Batch size for migration
179    pub batch_size: usize,
180    /// Rate limit (triples per second)
181    pub rate_limit: Option<usize>,
182    /// Migration rules
183    pub rules: Vec<MigrationRule>,
184}
185
186impl Default for MigrationPolicy {
187    fn default() -> Self {
188        MigrationPolicy {
189            auto_migrate: false,
190            trigger: MigrationTrigger::Manual,
191            batch_size: 10000,
192            rate_limit: Some(100000),
193            rules: Vec::new(),
194        }
195    }
196}
197
198/// Migration trigger
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub enum MigrationTrigger {
201    /// Manual migration only
202    Manual,
203    /// Storage threshold (percentage)
204    StorageThreshold(f64),
205    /// Time-based (hours)
206    Periodic(u32),
207    /// Cost-based optimization
208    CostOptimization,
209    /// Performance-based
210    Performance,
211}
212
213/// Migration rule
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct MigrationRule {
216    /// Rule name
217    pub name: String,
218    /// Source backend pattern
219    pub source: String,
220    /// Target backend
221    pub target: String,
222    /// Selection criteria
223    pub criteria: SelectionCriteria,
224    /// Priority
225    pub priority: u32,
226}
227
228/// Selection criteria for migration
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub enum SelectionCriteria {
231    /// Age-based (days)
232    Age(u32),
233    /// Access frequency
234    AccessFrequency { threshold: u32 },
235    /// Size-based (MB)
236    Size(usize),
237    /// Predicate pattern
238    PredicatePattern(String),
239    /// Custom function
240    Custom(String),
241}
242
243/// Virtual storage engine
244pub struct VirtualStorage {
245    config: VirtualStorageConfig,
246    /// Storage backends
247    backends: Arc<RwLock<HashMap<String, Arc<dyn StorageEngine>>>>,
248    /// Routing state
249    routing_state: Arc<RwLock<RoutingState>>,
250    /// Migration state
251    migration_state: Arc<RwLock<MigrationState>>,
252    /// Cache
253    cache: Arc<RwLock<StorageCache>>,
254    /// Statistics
255    stats: Arc<RwLock<VirtualStorageStats>>,
256}
257
258/// Routing state
259struct RoutingState {
260    /// Round-robin counter
261    round_robin_counter: usize,
262    /// Backend health status
263    backend_health: HashMap<String, BackendHealth>,
264    /// Active migrations
265    #[allow(dead_code)]
266    active_migrations: Vec<String>,
267}
268
269/// Backend health status
270#[derive(Debug, Clone, Serialize, Deserialize)]
271struct BackendHealth {
272    /// Is backend healthy
273    healthy: bool,
274    /// Last check time (timestamp in seconds)
275    last_check: u64,
276    /// Consecutive failures
277    failure_count: u32,
278    /// Average response time
279    avg_response_time_ms: f64,
280}
281
282/// Migration state
283struct MigrationState {
284    /// Active migrations
285    active_migrations: HashMap<String, MigrationJob>,
286    /// Migration history
287    history: Vec<MigrationRecord>,
288    /// Migration coordinator
289    #[allow(dead_code)]
290    coordinator: Option<MigrationCoordinator>,
291}
292
293/// Migration job
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct MigrationJob {
296    /// Job ID
297    id: String,
298    /// Source backend
299    source: String,
300    /// Target backend
301    target: String,
302    /// Progress
303    progress: MigrationProgress,
304    /// Start time (timestamp in seconds)
305    start_time: u64,
306    /// Status
307    status: MigrationStatus,
308}
309
310/// Migration progress
311#[derive(Debug, Clone, Serialize, Deserialize)]
312struct MigrationProgress {
313    /// Total triples to migrate
314    total_triples: u64,
315    /// Triples migrated
316    migrated_triples: u64,
317    /// Triples failed
318    failed_triples: u64,
319    /// Current batch
320    current_batch: u64,
321    /// Estimated completion time (seconds)
322    eta: Option<u64>,
323}
324
325/// Migration status
326#[derive(Debug, Clone, Serialize, Deserialize)]
327enum MigrationStatus {
328    Pending,
329    Running,
330    Paused,
331    Completed,
332    Failed(String),
333    Cancelled,
334}
335
336/// Migration record
337#[derive(Debug, Clone, Serialize, Deserialize)]
338struct MigrationRecord {
339    /// Job ID
340    job_id: String,
341    /// Source backend
342    source: String,
343    /// Target backend
344    target: String,
345    /// Start time
346    start_time: chrono::DateTime<chrono::Utc>,
347    /// End time
348    end_time: chrono::DateTime<chrono::Utc>,
349    /// Triples migrated
350    triples_migrated: u64,
351    /// Status
352    status: MigrationStatus,
353}
354
355/// Migration coordinator
356struct MigrationCoordinator {
357    /// Active workers
358    #[allow(dead_code)]
359    workers: Vec<tokio::task::JoinHandle<()>>,
360    /// Control channel
361    #[allow(dead_code)]
362    control_tx: tokio::sync::mpsc::Sender<MigrationControl>,
363}
364
365/// Migration control messages
366#[derive(Debug)]
367enum MigrationControl {
368    #[allow(dead_code)]
369    Pause,
370    #[allow(dead_code)]
371    Resume,
372    #[allow(dead_code)]
373    Cancel,
374    #[allow(dead_code)]
375    UpdateRateLimit(usize),
376}
377
378/// Storage cache
379struct StorageCache {
380    /// Triple cache
381    triple_cache: lru::LruCache<u64, Triple>,
382    /// Query result cache
383    query_cache: lru::LruCache<String, Vec<Triple>>,
384    /// Cache statistics
385    stats: CacheStats,
386}
387
388/// Cache statistics
389#[derive(Debug, Default)]
390struct CacheStats {
391    hits: u64,
392    misses: u64,
393    #[allow(dead_code)]
394    evictions: u64,
395}
396
397/// Virtual storage statistics
398#[derive(Debug, Default)]
399struct VirtualStorageStats {
400    /// Total operations
401    total_operations: u64,
402    /// Operations by backend
403    #[allow(dead_code)]
404    backend_operations: HashMap<String, u64>,
405    /// Migration statistics
406    #[allow(dead_code)]
407    migration_stats: MigrationStats,
408    /// Performance metrics
409    performance: PerformanceMetrics,
410}
411
412/// Migration statistics
413#[derive(Debug, Default)]
414struct MigrationStats {
415    /// Total migrations
416    #[allow(dead_code)]
417    total_migrations: u64,
418    /// Successful migrations
419    #[allow(dead_code)]
420    successful_migrations: u64,
421    /// Failed migrations
422    #[allow(dead_code)]
423    failed_migrations: u64,
424    /// Total triples migrated
425    #[allow(dead_code)]
426    total_triples_migrated: u64,
427    /// Total migration time
428    #[allow(dead_code)]
429    total_migration_time_sec: u64,
430}
431
432/// Performance metrics
433#[derive(Debug, Default)]
434struct PerformanceMetrics {
435    /// Average read latency
436    avg_read_latency_ms: f64,
437    /// Average write latency
438    avg_write_latency_ms: f64,
439    /// Query throughput
440    query_throughput_qps: f64,
441    /// Write throughput
442    #[allow(dead_code)]
443    write_throughput_tps: f64,
444}
445
446impl VirtualStorage {
447    /// Create new virtual storage
448    pub async fn new(config: VirtualStorageConfig) -> Result<Self, OxirsError> {
449        std::fs::create_dir_all(&config.path)?;
450
451        let cache_size = (config.cache_size_mb * 1024 * 1024) / 1000; // Approximate entries
452
453        Ok(VirtualStorage {
454            config: config.clone(),
455            backends: Arc::new(RwLock::new(HashMap::new())),
456            routing_state: Arc::new(RwLock::new(RoutingState {
457                round_robin_counter: 0,
458                backend_health: HashMap::new(),
459                active_migrations: Vec::new(),
460            })),
461            migration_state: Arc::new(RwLock::new(MigrationState {
462                active_migrations: HashMap::new(),
463                history: Vec::new(),
464                coordinator: None,
465            })),
466            cache: Arc::new(RwLock::new(StorageCache {
467                triple_cache: lru::LruCache::new(
468                    std::num::NonZeroUsize::new(cache_size).unwrap_or(
469                        std::num::NonZeroUsize::new(10000).expect("constant is non-zero"),
470                    ),
471                ),
472                query_cache: lru::LruCache::new(
473                    std::num::NonZeroUsize::new(1000).expect("constant is non-zero"),
474                ),
475                stats: CacheStats::default(),
476            })),
477            stats: Arc::new(RwLock::new(VirtualStorageStats::default())),
478        })
479    }
480
481    /// Initialize backends
482    pub async fn initialize_backends(&self) -> Result<(), OxirsError> {
483        for backend_config in &self.config.backends {
484            let backend = self.create_backend(backend_config).await?;
485            let mut backends = self.backends.write().await;
486            backends.insert(backend_config.name.clone(), backend);
487
488            // Initialize health status
489            let mut routing = self.routing_state.write().await;
490            routing.backend_health.insert(
491                backend_config.name.clone(),
492                BackendHealth {
493                    healthy: true,
494                    last_check: std::time::SystemTime::now()
495                        .duration_since(std::time::UNIX_EPOCH)
496                        .expect("SystemTime should be after UNIX_EPOCH")
497                        .as_secs(),
498                    failure_count: 0,
499                    avg_response_time_ms: 0.0,
500                },
501            );
502        }
503
504        // Start health monitoring
505        self.start_health_monitoring();
506
507        Ok(())
508    }
509
510    /// Start a migration job
511    pub async fn start_migration(
512        &self,
513        source: &str,
514        target: &str,
515        criteria: SelectionCriteria,
516    ) -> Result<String, OxirsError> {
517        let job_id = uuid::Uuid::new_v4().to_string();
518
519        let job = MigrationJob {
520            id: job_id.clone(),
521            source: source.to_string(),
522            target: target.to_string(),
523            progress: MigrationProgress {
524                total_triples: 0,
525                migrated_triples: 0,
526                failed_triples: 0,
527                current_batch: 0,
528                eta: None,
529            },
530            start_time: std::time::SystemTime::now()
531                .duration_since(std::time::UNIX_EPOCH)
532                .expect("SystemTime should be after UNIX_EPOCH")
533                .as_secs(),
534            status: MigrationStatus::Pending,
535        };
536
537        let mut migration_state = self.migration_state.write().await;
538        migration_state
539            .active_migrations
540            .insert(job_id.clone(), job);
541
542        // Start migration worker
543        self.spawn_migration_worker(
544            job_id.clone(),
545            source.to_string(),
546            target.to_string(),
547            criteria,
548        )
549        .await?;
550
551        Ok(job_id)
552    }
553
554    /// Get migration status
555    pub async fn get_migration_status(&self, job_id: &str) -> Result<MigrationJob, OxirsError> {
556        let migration_state = self.migration_state.read().await;
557        migration_state
558            .active_migrations
559            .get(job_id)
560            .cloned()
561            .ok_or_else(|| OxirsError::Store(format!("Migration job not found: {job_id}")))
562    }
563
564    /// Create backend instance
565    async fn create_backend(
566        &self,
567        config: &BackendConfig,
568    ) -> Result<Arc<dyn StorageEngine>, OxirsError> {
569        match &config.backend_type {
570            BackendType::Tiered => {
571                // Tiered persistent backend is not available in this build.
572                Err(OxirsError::Store(
573                    "Tiered backend persistent storage is not available in this build".to_string(),
574                ))
575            }
576            BackendType::Columnar => {
577                // Create columnar backend
578                Err(OxirsError::Store(
579                    "Columnar backend not yet integrated".to_string(),
580                ))
581            }
582            BackendType::Immutable => {
583                // Create immutable backend
584                Err(OxirsError::Store(
585                    "Immutable backend not yet integrated".to_string(),
586                ))
587            }
588            BackendType::Temporal => {
589                // Create temporal backend
590                Err(OxirsError::Store(
591                    "Temporal backend not yet integrated".to_string(),
592                ))
593            }
594            BackendType::Remote { endpoint } => {
595                // Create remote backend proxy
596                Err(OxirsError::Store(format!(
597                    "Remote backend not implemented: {endpoint}"
598                )))
599            }
600            BackendType::Cloud { provider: _ } => {
601                // Create cloud backend
602                Err(OxirsError::Store(
603                    "Cloud backend not implemented".to_string(),
604                ))
605            }
606        }
607    }
608
609    /// Route read operation
610    async fn route_read(&self) -> Result<Vec<String>, OxirsError> {
611        let routing_state = self.routing_state.read().await;
612        let backends = self.backends.read().await;
613
614        match self.config.routing.read_strategy {
615            ReadStrategy::FirstAvailable => {
616                // Return first healthy backend
617                for (name, health) in &routing_state.backend_health {
618                    if health.healthy && backends.contains_key(name) {
619                        return Ok(vec![name.clone()]);
620                    }
621                }
622                Err(OxirsError::Store(
623                    "No healthy backends available".to_string(),
624                ))
625            }
626            ReadStrategy::RoundRobin => {
627                // Round-robin selection
628                let healthy_backends: Vec<_> = routing_state
629                    .backend_health
630                    .iter()
631                    .filter(|(name, health)| health.healthy && backends.contains_key(*name))
632                    .map(|(name, _)| name.clone())
633                    .collect();
634
635                if healthy_backends.is_empty() {
636                    return Err(OxirsError::Store(
637                        "No healthy backends available".to_string(),
638                    ));
639                }
640
641                let index = routing_state.round_robin_counter % healthy_backends.len();
642                Ok(vec![healthy_backends[index].clone()])
643            }
644            ReadStrategy::Broadcast => {
645                // Query all healthy backends
646                Ok(routing_state
647                    .backend_health
648                    .iter()
649                    .filter(|(name, health)| health.healthy && backends.contains_key(*name))
650                    .map(|(name, _)| name.clone())
651                    .collect())
652            }
653            _ => Ok(vec!["primary".to_string()]), // Default to primary
654        }
655    }
656
657    /// Route write operation
658    async fn route_write(&self) -> Result<Vec<String>, OxirsError> {
659        let routing_state = self.routing_state.read().await;
660        let backends = self.backends.read().await;
661
662        match self.config.routing.write_strategy {
663            WriteStrategy::All => {
664                // Write to all writable backends
665                Ok(self
666                    .config
667                    .backends
668                    .iter()
669                    .filter(|b| !b.read_only)
670                    .filter(|b| {
671                        routing_state
672                            .backend_health
673                            .get(&b.name)
674                            .map(|h| h.healthy)
675                            .unwrap_or(false)
676                    })
677                    .filter(|b| backends.contains_key(&b.name))
678                    .map(|b| b.name.clone())
679                    .collect())
680            }
681            WriteStrategy::PrimaryOnly => Ok(vec!["primary".to_string()]),
682            WriteStrategy::Quorum { n } => {
683                // Select N backends
684                let writable: Vec<_> = self
685                    .config
686                    .backends
687                    .iter()
688                    .filter(|b| !b.read_only)
689                    .filter(|b| {
690                        routing_state
691                            .backend_health
692                            .get(&b.name)
693                            .map(|h| h.healthy)
694                            .unwrap_or(false)
695                    })
696                    .filter(|b| backends.contains_key(&b.name))
697                    .take(n)
698                    .map(|b| b.name.clone())
699                    .collect();
700
701                if writable.len() < n {
702                    return Err(OxirsError::Store(format!(
703                        "Not enough healthy backends for quorum: need {}, have {}",
704                        n,
705                        writable.len()
706                    )));
707                }
708
709                Ok(writable)
710            }
711            WriteStrategy::Selective => {
712                // Selective routing based on data characteristics
713                Ok(vec!["primary".to_string()]) // Simplified
714            }
715        }
716    }
717
718    /// Start health monitoring
719    fn start_health_monitoring(&self) {
720        let backends = self.backends.clone();
721        let routing_state = self.routing_state.clone();
722
723        tokio::spawn(async move {
724            let mut interval = tokio::time::interval(std::time::Duration::from_secs(30));
725
726            loop {
727                interval.tick().await;
728
729                // Check health of each backend
730                let backend_list = backends.read().await;
731                for (name, backend) in backend_list.iter() {
732                    let start = std::time::Instant::now();
733                    let health_check = backend.stats().await;
734                    let elapsed = start.elapsed();
735
736                    let mut routing = routing_state.write().await;
737                    if let Some(health) = routing.backend_health.get_mut(name) {
738                        health.last_check = std::time::SystemTime::now()
739                            .duration_since(std::time::UNIX_EPOCH)
740                            .expect("SystemTime should be after UNIX_EPOCH")
741                            .as_secs();
742                        health.avg_response_time_ms = elapsed.as_millis() as f64;
743
744                        if health_check.is_ok() {
745                            health.healthy = true;
746                            health.failure_count = 0;
747                        } else {
748                            health.failure_count += 1;
749                            if health.failure_count >= 3 {
750                                health.healthy = false;
751                            }
752                        }
753                    }
754                }
755            }
756        });
757    }
758
759    /// Spawn migration worker
760    async fn spawn_migration_worker(
761        &self,
762        job_id: String,
763        _source: String,
764        _target: String,
765        _criteria: SelectionCriteria,
766    ) -> Result<(), OxirsError> {
767        let _backends = self.backends.clone();
768        let migration_state = self.migration_state.clone();
769        let _config = self.config.clone();
770
771        tokio::spawn(async move {
772            // Migration logic would go here
773            // This is a simplified placeholder
774            let mut state = migration_state.write().await;
775            if let Some(job) = state.active_migrations.get_mut(&job_id) {
776                job.status = MigrationStatus::Running;
777            }
778
779            // Simulate migration
780            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
781
782            // Update status
783            let mut state = migration_state.write().await;
784            if let Some(job) = state.active_migrations.get_mut(&job_id) {
785                job.status = MigrationStatus::Completed;
786                job.progress.migrated_triples = 1000; // Simulated
787            }
788        });
789
790        Ok(())
791    }
792}
793
794#[async_trait]
795impl StorageEngine for VirtualStorage {
796    async fn init(&mut self, _config: super::StorageConfig) -> Result<(), OxirsError> {
797        self.initialize_backends().await
798    }
799
800    async fn store_triple(&self, triple: &Triple) -> Result<(), OxirsError> {
801        let start = std::time::Instant::now();
802
803        // Route write
804        let target_backends = self.route_write().await?;
805        let backends = self.backends.read().await;
806
807        let mut errors = Vec::new();
808        for backend_name in &target_backends {
809            if let Some(backend) = backends.get(backend_name) {
810                if let Err(e) = backend.store_triple(triple).await {
811                    errors.push((backend_name.clone(), e));
812                }
813            }
814        }
815
816        // Update cache
817        if self.config.caching {
818            let mut cache = self.cache.write().await;
819            let hash = self.hash_triple(triple);
820            cache.triple_cache.put(hash, triple.clone());
821        }
822
823        // Update statistics
824        let elapsed = start.elapsed();
825        let mut stats = self.stats.write().await;
826        stats.total_operations += 1;
827        stats.performance.avg_write_latency_ms = (stats.performance.avg_write_latency_ms
828            * (stats.total_operations - 1) as f64
829            + elapsed.as_millis() as f64)
830            / stats.total_operations as f64;
831
832        // Handle errors based on write strategy
833        match self.config.routing.write_strategy {
834            WriteStrategy::All if !errors.is_empty() => {
835                return Err(OxirsError::Store(format!(
836                    "Failed to write to backends: {errors:?}"
837                )));
838            }
839            WriteStrategy::Quorum { n } if target_backends.len() - errors.len() < n => {
840                return Err(OxirsError::Store(format!(
841                    "Quorum write failed: {} successes, needed {}",
842                    target_backends.len() - errors.len(),
843                    n
844                )));
845            }
846            _ => {}
847        }
848
849        Ok(())
850    }
851
852    async fn store_triples(&self, triples: &[Triple]) -> Result<(), OxirsError> {
853        // Batch operation - could be optimized
854        for triple in triples {
855            self.store_triple(triple).await?;
856        }
857        Ok(())
858    }
859
860    async fn query_triples(&self, pattern: &TriplePattern) -> Result<Vec<Triple>, OxirsError> {
861        let start = std::time::Instant::now();
862
863        // Check cache first
864        if self.config.caching {
865            let pattern_key = format!("{pattern:?}");
866            let mut cache = self.cache.write().await;
867            if let Some(cached) = cache.query_cache.get(&pattern_key).cloned() {
868                cache.stats.hits += 1;
869                return Ok(cached);
870            }
871            cache.stats.misses += 1;
872        }
873
874        // Route read
875        let target_backends = self.route_read().await?;
876        let backends = self.backends.read().await;
877
878        let mut all_results = Vec::new();
879        let mut seen = std::collections::HashSet::new();
880
881        for backend_name in &target_backends {
882            if let Some(backend) = backends.get(backend_name) {
883                match backend.query_triples(pattern).await {
884                    Ok(results) => {
885                        for triple in results {
886                            let hash = self.hash_triple(&triple);
887                            if seen.insert(hash) {
888                                all_results.push(triple);
889                            }
890                        }
891                    }
892                    Err(e) => {
893                        tracing::warn!("Query failed on backend {}: {}", backend_name, e);
894                    }
895                }
896            }
897        }
898
899        // Update cache
900        if self.config.caching && !all_results.is_empty() {
901            let pattern_key = format!("{pattern:?}");
902            let mut cache = self.cache.write().await;
903            cache.query_cache.put(pattern_key, all_results.clone());
904        }
905
906        // Update statistics
907        let elapsed = start.elapsed();
908        let mut stats = self.stats.write().await;
909        stats.total_operations += 1;
910        stats.performance.avg_read_latency_ms = (stats.performance.avg_read_latency_ms
911            * (stats.total_operations - 1) as f64
912            + elapsed.as_millis() as f64)
913            / stats.total_operations as f64;
914
915        Ok(all_results)
916    }
917
918    async fn delete_triples(&self, pattern: &TriplePattern) -> Result<usize, OxirsError> {
919        // Route to all writable backends
920        let target_backends = self.route_write().await?;
921        let backends = self.backends.read().await;
922
923        let mut total_deleted = 0;
924        for backend_name in &target_backends {
925            if let Some(backend) = backends.get(backend_name) {
926                match backend.delete_triples(pattern).await {
927                    Ok(count) => total_deleted = total_deleted.max(count),
928                    Err(e) => {
929                        tracing::warn!("Delete failed on backend {}: {}", backend_name, e);
930                    }
931                }
932            }
933        }
934
935        // Invalidate cache
936        if self.config.caching {
937            let mut cache = self.cache.write().await;
938            cache.query_cache.clear();
939            // Could be more selective about cache invalidation
940        }
941
942        Ok(total_deleted)
943    }
944
945    async fn stats(&self) -> Result<super::StorageStats, OxirsError> {
946        let backends = self.backends.read().await;
947        let stats = self.stats.read().await;
948
949        // Aggregate stats from all backends
950        let mut total_triples = 0u64;
951        let mut total_size = 0u64;
952
953        for (_, backend) in backends.iter() {
954            if let Ok(backend_stats) = backend.stats().await {
955                total_triples += backend_stats.total_triples;
956                total_size += backend_stats.total_size_bytes;
957            }
958        }
959
960        Ok(super::StorageStats {
961            total_triples,
962            total_size_bytes: total_size,
963            tier_stats: super::TierStats {
964                hot: super::TierStat {
965                    triple_count: 0,
966                    size_bytes: 0,
967                    hit_rate: 0.0,
968                    avg_access_time_us: 0,
969                },
970                warm: super::TierStat {
971                    triple_count: 0,
972                    size_bytes: 0,
973                    hit_rate: 0.0,
974                    avg_access_time_us: 0,
975                },
976                cold: super::TierStat {
977                    triple_count: 0,
978                    size_bytes: 0,
979                    hit_rate: 0.0,
980                    avg_access_time_us: 0,
981                },
982                archive: super::TierStat {
983                    triple_count: 0,
984                    size_bytes: 0,
985                    hit_rate: 0.0,
986                    avg_access_time_us: 0,
987                },
988            },
989            compression_ratio: 1.0,
990            query_metrics: super::QueryMetrics {
991                avg_query_time_ms: stats.performance.avg_read_latency_ms,
992                p99_query_time_ms: stats.performance.avg_read_latency_ms * 2.0, // Estimate
993                qps: stats.performance.query_throughput_qps,
994                cache_hit_rate: if self.config.caching {
995                    let cache = self.cache.read().await;
996                    let total = cache.stats.hits + cache.stats.misses;
997                    if total > 0 {
998                        (cache.stats.hits as f64 / total as f64) * 100.0
999                    } else {
1000                        0.0
1001                    }
1002                } else {
1003                    0.0
1004                },
1005            },
1006        })
1007    }
1008
1009    async fn optimize(&self) -> Result<(), OxirsError> {
1010        let backends = self.backends.read().await;
1011
1012        // Optimize all backends
1013        for (name, backend) in backends.iter() {
1014            if let Err(e) = backend.optimize().await {
1015                tracing::warn!("Optimization failed on backend {}: {}", name, e);
1016            }
1017        }
1018
1019        // Clear cache to force refresh
1020        if self.config.caching {
1021            let mut cache = self.cache.write().await;
1022            cache.query_cache.clear();
1023        }
1024
1025        Ok(())
1026    }
1027
1028    async fn backup(&self, path: &Path) -> Result<(), OxirsError> {
1029        std::fs::create_dir_all(path)?;
1030
1031        let backends = self.backends.read().await;
1032
1033        // Backup each backend
1034        for (name, backend) in backends.iter() {
1035            let backend_path = path.join(name);
1036            backend.backup(&backend_path).await?;
1037        }
1038
1039        // Save virtual storage metadata
1040        let metadata = VirtualStorageMetadata {
1041            config: self.config.clone(),
1042            migration_history: {
1043                let state = self.migration_state.read().await;
1044                state.history.clone()
1045            },
1046        };
1047
1048        let metadata_path = path.join("virtual_metadata.json");
1049        let metadata_json = serde_json::to_string_pretty(&metadata)
1050            .map_err(|e| OxirsError::Serialize(e.to_string()))?;
1051        std::fs::write(metadata_path, metadata_json)?;
1052
1053        Ok(())
1054    }
1055
1056    async fn restore(&self, path: &Path) -> Result<(), OxirsError> {
1057        // Load metadata
1058        let metadata_path = path.join("virtual_metadata.json");
1059        let metadata_json = std::fs::read_to_string(metadata_path)?;
1060        let metadata: VirtualStorageMetadata =
1061            serde_json::from_str(&metadata_json).map_err(|e| OxirsError::Parse(e.to_string()))?;
1062
1063        let backends = self.backends.read().await;
1064
1065        // Restore each backend
1066        for (name, backend) in backends.iter() {
1067            let backend_path = path.join(name);
1068            if backend_path.exists() {
1069                backend.restore(&backend_path).await?;
1070            }
1071        }
1072
1073        // Restore migration history
1074        let mut state = self.migration_state.write().await;
1075        state.history = metadata.migration_history;
1076
1077        Ok(())
1078    }
1079}
1080
1081/// Virtual storage metadata
1082#[derive(Debug, Serialize, Deserialize)]
1083struct VirtualStorageMetadata {
1084    config: VirtualStorageConfig,
1085    migration_history: Vec<MigrationRecord>,
1086}
1087
1088impl VirtualStorage {
1089    /// Hash a triple for deduplication
1090    fn hash_triple(&self, triple: &Triple) -> u64 {
1091        use std::hash::{Hash, Hasher};
1092        let mut hasher = std::collections::hash_map::DefaultHasher::new();
1093        triple.hash(&mut hasher);
1094        hasher.finish()
1095    }
1096}
1097
1098#[cfg(test)]
1099mod tests {
1100    use super::*;
1101    use crate::model::{Literal, NamedNode};
1102
1103    #[tokio::test]
1104    #[ignore = "Virtual storage backends not yet implemented - all backend types are disabled"]
1105    async fn test_virtual_storage() {
1106        let dir = tempfile::tempdir().expect("tempdir");
1107        let test_dir = dir.path().join("oxirs_virtual_test").display().to_string();
1108
1109        let mut config = VirtualStorageConfig {
1110            path: PathBuf::from(&test_dir),
1111            ..Default::default()
1112        };
1113
1114        // Update backend config with test-specific paths
1115        if let Some(backend) = config.backends.get_mut(0) {
1116            backend.config = serde_json::json!({
1117                "enable_tiering": true,
1118                "enable_columnar": false,
1119                "enable_temporal": false,
1120                "compression": {
1121                    "Zstd": { "level": 3 }
1122                },
1123                "tiers": {
1124                    "hot_tier": {
1125                        "max_size_mb": 1024,
1126                        "eviction_policy": "Lru",
1127                        "ttl_seconds": 3600
1128                    },
1129                    "warm_tier": {
1130                        "path": format!("{test_dir}/warm"),
1131                        "max_size_gb": 10,
1132                        "promotion_threshold": 10,
1133                        "demotion_threshold_days": 7
1134                    },
1135                    "cold_tier": {
1136                        "path": format!("{test_dir}/cold"),
1137                        "max_size_tb": 1,
1138                        "compression_level": 9,
1139                        "archive_threshold_days": 90
1140                    },
1141                    "archive_tier": {
1142                        "backend": { "Local": format!("{test_dir}/archive") },
1143                        "retention_years": 7,
1144                        "immutable": true
1145                    }
1146                },
1147                "cache_size_mb": 512
1148            });
1149        }
1150
1151        let storage = VirtualStorage::new(config)
1152            .await
1153            .expect("async operation should succeed");
1154        storage
1155            .initialize_backends()
1156            .await
1157            .expect("async operation should succeed");
1158
1159        // Create test triple
1160        let triple = Triple::new(
1161            NamedNode::new("http://example.org/s").expect("valid IRI"),
1162            NamedNode::new("http://example.org/p").expect("valid IRI"),
1163            crate::model::Object::Literal(Literal::new("test")),
1164        );
1165
1166        // Store triple
1167        storage
1168            .store_triple(&triple)
1169            .await
1170            .expect("async operation should succeed");
1171
1172        // Query triple
1173        let pattern = TriplePattern::new(None, None, None);
1174        let results = storage
1175            .query_triples(&pattern)
1176            .await
1177            .expect("async operation should succeed");
1178        assert_eq!(results.len(), 1);
1179        assert_eq!(results[0], triple);
1180    }
1181}