mirage/storage/
backend.rs1use anyhow::Result;
2use std::path::Path;
3
4use super::{CfgBlockData, DocumentInfo, StorageTrait};
5
6#[cfg(feature = "backend-sqlite")]
7use super::sqlite_backend::SqliteStorage;
8
9#[derive(Debug)]
16#[allow(clippy::large_enum_variant)]
17pub enum Backend {
18 #[cfg(feature = "backend-sqlite")]
20 Sqlite(SqliteStorage),
21}
22
23impl Backend {
24 pub fn detect_and_open(db_path: &Path) -> Result<Self> {
47 use magellan::migrate_backend_cmd::detect_backend_format;
48
49 let sqlite_detected = detect_backend_format(db_path).is_ok();
51
52 #[cfg(feature = "backend-sqlite")]
53 {
54 if sqlite_detected {
55 SqliteStorage::open(db_path).map(Backend::Sqlite)
56 } else {
57 Err(anyhow::anyhow!(
58 "Unsupported database format; use a SQLite .db"
59 ))
60 }
61 }
62
63 #[cfg(not(feature = "backend-sqlite"))]
64 {
65 let _ = sqlite_detected;
66 Err(anyhow::anyhow!("No storage backend feature enabled"))
67 }
68 }
69
70 pub fn is_sqlite(&self) -> bool {
72 match self {
73 #[cfg(feature = "backend-sqlite")]
74 Backend::Sqlite(_) => true,
75 }
76 }
77
78 pub fn get_cfg_blocks(&self, function_id: i64) -> Result<Vec<CfgBlockData>> {
80 match self {
81 #[cfg(feature = "backend-sqlite")]
82 Backend::Sqlite(s) => s.get_cfg_blocks(function_id),
83 }
84 }
85
86 pub fn get_entity(&self, entity_id: i64) -> Option<sqlitegraph::GraphEntity> {
88 match self {
89 #[cfg(feature = "backend-sqlite")]
90 Backend::Sqlite(s) => s.get_entity(entity_id),
91 }
92 }
93
94 pub fn get_cached_paths(&self, function_id: i64) -> Result<Option<Vec<crate::cfg::Path>>> {
96 match self {
97 #[cfg(feature = "backend-sqlite")]
98 Backend::Sqlite(s) => s.get_cached_paths(function_id),
99 }
100 }
101
102 pub fn get_callees(&self, function_id: i64) -> Result<Vec<i64>> {
104 match self {
105 #[cfg(feature = "backend-sqlite")]
106 Backend::Sqlite(s) => s.get_callees(function_id),
107 }
108 }
109
110 pub fn list_source_documents(&self) -> Result<Vec<DocumentInfo>> {
112 match self {
113 #[cfg(feature = "backend-sqlite")]
114 Backend::Sqlite(s) => s.list_source_documents(),
115 }
116 }
117}
118
119impl StorageTrait for Backend {
121 fn get_cfg_blocks(&self, function_id: i64) -> Result<Vec<CfgBlockData>> {
122 self.get_cfg_blocks(function_id)
123 }
124
125 fn get_entity(&self, entity_id: i64) -> Option<sqlitegraph::GraphEntity> {
126 self.get_entity(entity_id)
127 }
128
129 fn get_cached_paths(&self, function_id: i64) -> Result<Option<Vec<crate::cfg::Path>>> {
130 self.get_cached_paths(function_id)
131 }
132
133 fn get_callees(&self, function_id: i64) -> Result<Vec<i64>> {
134 self.get_callees(function_id)
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum BackendFormat {
144 SQLite,
146 Unknown,
148}
149
150impl BackendFormat {
151 pub fn detect(path: &Path) -> Result<Self> {
159 if !path.exists() {
160 return Ok(BackendFormat::Unknown);
161 }
162
163 let mut file = std::fs::File::open(path)?;
164 let mut header = [0u8; 16];
165 let bytes_read = std::io::Read::read(&mut file, &mut header)?;
166
167 if bytes_read < header.len() {
168 return Ok(BackendFormat::Unknown);
169 }
170
171 Ok(if &header[..15] == b"SQLite format 3" {
173 BackendFormat::SQLite
174 } else {
175 BackendFormat::Unknown
176 })
177 }
178}
179
180#[cfg(all(test, feature = "sqlite"))]
181mod tests {
182 use super::*;
183 use std::path::Path;
184
185 #[test]
186 fn test_backend_detect_sqlite_header() {
187 use std::io::Write;
188
189 let temp_file = tempfile::NamedTempFile::new().unwrap();
190 let mut file = std::fs::File::create(temp_file.path()).unwrap();
191 file.write_all(b"SQLite format 3\0").unwrap();
192 file.sync_all().unwrap();
193
194 let backend = BackendFormat::detect(temp_file.path()).unwrap();
195 assert_eq!(
196 backend,
197 BackendFormat::SQLite,
198 "Should detect SQLite format"
199 );
200 }
201
202 #[test]
203 fn test_backend_detect_nonexistent_file() {
204 let backend = BackendFormat::detect(Path::new("/nonexistent/path/to/file.db")).unwrap();
205 assert_eq!(
206 backend,
207 BackendFormat::Unknown,
208 "Non-existent file should be Unknown"
209 );
210 }
211
212 #[test]
213 fn test_backend_detect_empty_file() {
214 let temp_file = tempfile::NamedTempFile::new().unwrap();
215
216 let backend = BackendFormat::detect(temp_file.path()).unwrap();
217 assert_eq!(
218 backend,
219 BackendFormat::Unknown,
220 "Empty file should be Unknown"
221 );
222 }
223
224 #[test]
225 fn test_backend_detect_partial_header() {
226 use std::io::Write;
227
228 let temp_file = tempfile::NamedTempFile::new().unwrap();
229 let mut file = std::fs::File::create(temp_file.path()).unwrap();
230 file.write_all(b"SQLite").unwrap();
231 file.sync_all().unwrap();
232
233 let backend = BackendFormat::detect(temp_file.path()).unwrap();
234 assert_eq!(
235 backend,
236 BackendFormat::Unknown,
237 "Partial header should be Unknown"
238 );
239 }
240
241 #[test]
242 fn test_backend_equality() {
243 assert_eq!(BackendFormat::SQLite, BackendFormat::SQLite);
244 assert_eq!(BackendFormat::Unknown, BackendFormat::Unknown);
245
246 assert_ne!(BackendFormat::SQLite, BackendFormat::Unknown);
247 }
248}