Skip to main content

phago_runtime/
backend.rs

1//! Graph backend configuration and factory.
2//!
3//! Provides a unified interface for selecting and configuring graph storage backends.
4
5use crate::topology_impl::PetTopologyGraph;
6use phago_core::topology::TopologyGraph;
7
8#[cfg(feature = "sqlite")]
9use std::path::PathBuf;
10
11/// Configuration for graph backend selection.
12#[derive(Debug, Clone)]
13pub enum BackendConfig {
14    /// In-memory petgraph backend (default, fast, no persistence).
15    InMemory,
16
17    /// SQLite-backed persistent storage.
18    #[cfg(feature = "sqlite")]
19    Sqlite {
20        /// Path to the SQLite database file.
21        /// If None, uses an in-memory SQLite database.
22        path: Option<PathBuf>,
23        /// Node cache size for frequently accessed nodes.
24        cache_size: usize,
25    },
26}
27
28impl Default for BackendConfig {
29    fn default() -> Self {
30        BackendConfig::InMemory
31    }
32}
33
34impl BackendConfig {
35    /// Create an in-memory backend configuration.
36    pub fn in_memory() -> Self {
37        BackendConfig::InMemory
38    }
39
40    /// Create an SQLite backend configuration with a file path.
41    #[cfg(feature = "sqlite")]
42    pub fn sqlite(path: impl Into<PathBuf>) -> Self {
43        BackendConfig::Sqlite {
44            path: Some(path.into()),
45            cache_size: 1000,
46        }
47    }
48
49    /// Create an SQLite backend configuration with in-memory storage.
50    #[cfg(feature = "sqlite")]
51    pub fn sqlite_in_memory() -> Self {
52        BackendConfig::Sqlite {
53            path: None,
54            cache_size: 1000,
55        }
56    }
57
58    /// Set the cache size for SQLite backend.
59    #[cfg(feature = "sqlite")]
60    pub fn with_cache_size(mut self, size: usize) -> Self {
61        if let BackendConfig::Sqlite { cache_size, .. } = &mut self {
62            *cache_size = size;
63        }
64        self
65    }
66}
67
68/// Trait object for graph backends.
69///
70/// This allows storing different backend implementations behind a single type.
71pub type DynTopologyGraph = Box<dyn TopologyGraph + Send + Sync>;
72
73/// Create a graph backend from configuration.
74///
75/// # Errors
76/// Returns an error if the backend cannot be created (e.g., SQLite file issues).
77pub fn create_backend(config: &BackendConfig) -> Result<DynTopologyGraph, BackendError> {
78    match config {
79        BackendConfig::InMemory => Ok(Box::new(PetTopologyGraph::new())),
80
81        #[cfg(feature = "sqlite")]
82        BackendConfig::Sqlite { path, cache_size } => {
83            use crate::sqlite_topology::SqliteTopologyGraph;
84
85            let graph = if let Some(p) = path {
86                SqliteTopologyGraph::open(p)
87                    .map_err(|e| BackendError::SqliteError(e.to_string()))?
88            } else {
89                SqliteTopologyGraph::new_in_memory()
90                    .map_err(|e| BackendError::SqliteError(e.to_string()))?
91            };
92
93            Ok(Box::new(graph.with_cache_size(*cache_size)))
94        }
95    }
96}
97
98/// Errors that can occur when creating graph backends.
99#[derive(Debug, Clone)]
100pub enum BackendError {
101    /// SQLite-specific error.
102    #[cfg(feature = "sqlite")]
103    SqliteError(String),
104
105    /// Generic backend error.
106    Other(String),
107}
108
109impl std::fmt::Display for BackendError {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            #[cfg(feature = "sqlite")]
113            BackendError::SqliteError(msg) => write!(f, "SQLite error: {}", msg),
114            BackendError::Other(msg) => write!(f, "Backend error: {}", msg),
115        }
116    }
117}
118
119impl std::error::Error for BackendError {}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn create_in_memory_backend() {
127        let config = BackendConfig::in_memory();
128        let backend = create_backend(&config).unwrap();
129        assert_eq!(backend.node_count(), 0);
130    }
131
132    #[cfg(feature = "sqlite")]
133    #[test]
134    fn create_sqlite_in_memory_backend() {
135        let config = BackendConfig::sqlite_in_memory();
136        let backend = create_backend(&config).unwrap();
137        assert_eq!(backend.node_count(), 0);
138    }
139
140    #[cfg(feature = "sqlite")]
141    #[test]
142    fn create_sqlite_file_backend() {
143        let tmp = std::env::temp_dir().join("phago_backend_test.db");
144        let config = BackendConfig::sqlite(&tmp);
145        let backend = create_backend(&config).unwrap();
146        assert_eq!(backend.node_count(), 0);
147        std::fs::remove_file(&tmp).ok();
148    }
149}