#![cfg(feature = "backend-geometric")]
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use magellan::graph::geometric_backend::{
CfgBlock as MagellanCfgBlock, GeometricBackend as MagellanGeometricBackend, SymbolInfo,
};
use crate::storage::{CfgBlockData, StorageTrait};
pub struct GeometricStorage {
inner: MagellanGeometricBackend,
db_path: PathBuf,
}
impl std::fmt::Debug for GeometricStorage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GeometricStorage")
.field("db_path", &self.db_path)
.finish_non_exhaustive()
}
}
impl GeometricStorage {
pub fn open(db_path: &Path) -> Result<Self> {
if !db_path.exists() {
anyhow::bail!("Database not found: {}", db_path.display());
}
match db_path.extension().and_then(|e| e.to_str()) {
Some("geo") => {}
_ => {
anyhow::bail!("File does not have .geo extension: {}", db_path.display());
}
}
let inner = MagellanGeometricBackend::open(db_path)
.map_err(|e| anyhow::anyhow!("Failed to open geometric backend: {}", e))?;
Ok(Self {
inner,
db_path: db_path.to_path_buf(),
})
}
#[allow(dead_code)]
pub fn create(db_path: &Path) -> Result<Self> {
let inner = MagellanGeometricBackend::create(db_path)
.map_err(|e| anyhow::anyhow!("Failed to create geometric backend: {}", e))?;
Ok(Self {
inner,
db_path: db_path.to_path_buf(),
})
}
pub fn inner(&self) -> &MagellanGeometricBackend {
&self.inner
}
pub fn find_symbols_by_name(&self, name: &str) -> Vec<SymbolInfo> {
self.inner.find_symbols_by_name_info(name)
}
pub fn find_symbol_by_fqn(&self, fqn: &str) -> Option<SymbolInfo> {
self.inner.find_symbol_by_fqn_info(fqn)
}
pub fn find_symbol_id_by_name_and_path(&self, name: &str, path: &str) -> Option<u64> {
self.inner.find_symbol_id_by_name_and_path(name, path)
}
pub fn get_cfg_blocks_for_function(&self, function_id: i64) -> Vec<MagellanCfgBlock> {
self.inner.get_cfg_blocks_for_function(function_id)
}
pub fn get_all_edges(&self) -> Vec<geographdb_core::storage::EdgeRec> {
self.inner.get_all_edges()
}
pub fn complete_fqn_prefix(&self, prefix: &str, limit: usize) -> Vec<String> {
self.inner.complete_fqn_prefix(prefix, limit)
}
pub fn get_stats(&self) -> Result<GeometricStats> {
self.inner.reload_from_disk()?;
let stats = self.inner.get_geometric_stats();
Ok(GeometricStats {
symbol_count: stats.symbol_count as usize,
cfg_block_count: stats.cfg_block_count as usize,
})
}
}
#[derive(Debug, Clone)]
pub struct GeometricStats {
pub symbol_count: usize,
pub cfg_block_count: usize,
}
impl StorageTrait for GeometricStorage {
fn get_cfg_blocks(&self, function_id: i64) -> Result<Vec<CfgBlockData>> {
let blocks = self.inner.get_cfg_blocks_for_function(function_id);
tracing::debug!(
"get_cfg_blocks for function_id {}: found {} blocks",
function_id,
blocks.len()
);
Ok(blocks
.into_iter()
.map(|block| {
CfgBlockData {
id: block.id as i64,
kind: block.block_kind,
terminator: block.terminator,
byte_start: block.byte_start,
byte_end: block.byte_end,
start_line: block.start_line,
start_col: block.start_col,
end_line: block.end_line,
end_col: block.end_col,
coord_x: block.dominator_depth as i64,
coord_y: block.loop_nesting as i64,
coord_z: block.branch_count as i64,
}
})
.collect())
}
fn get_entity(&self, entity_id: i64) -> Option<sqlitegraph::GraphEntity> {
if let Some(info) = self.inner.find_symbol_by_id_info(entity_id as u64) {
let data = serde_json::json!({
"fqn": info.fqn,
"byte_start": info.byte_start,
"byte_end": info.byte_end,
"start_line": info.start_line,
"start_col": info.start_col,
"end_line": info.end_line,
"end_col": info.end_col,
});
Some(sqlitegraph::GraphEntity {
id: entity_id,
name: info.name,
kind: format!("{:?}", info.kind),
file_path: Some(info.file_path),
data,
})
} else {
None
}
}
fn get_cached_paths(&self, _function_id: i64) -> Result<Option<Vec<crate::cfg::Path>>> {
Ok(None)
}
fn get_callees(&self, function_id: i64) -> Result<Vec<i64>> {
let callees = self.inner.get_callees(function_id as u64);
Ok(callees.into_iter().map(|id| id as i64).collect())
}
}
fn convert_terminator_to_enum(terminator: &str) -> crate::cfg::Terminator {
match terminator {
"Return" => crate::cfg::Terminator::Return,
"Unreachable" => crate::cfg::Terminator::Unreachable,
"Fallthrough" => crate::cfg::Terminator::Goto { target: 0 }, "Conditional" => crate::cfg::Terminator::SwitchInt {
targets: vec![],
otherwise: 0,
},
_ => crate::cfg::Terminator::Abort(terminator.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_geo_db() -> (TempDir, PathBuf) {
let temp_dir = TempDir::new().unwrap();
let geo_path = temp_dir.path().join("test.geo");
let _backend = MagellanGeometricBackend::create(&geo_path)
.expect("Failed to create test geo database");
(temp_dir, geo_path)
}
#[test]
fn test_geometric_storage_open_valid_file() {
let (_temp_dir, geo_path) = create_test_geo_db();
let result = GeometricStorage::open(&geo_path);
assert!(result.is_ok(), "Should open valid .geo file");
}
#[test]
fn test_geometric_storage_open_nonexistent_file() {
let temp_dir = TempDir::new().unwrap();
let geo_path = temp_dir.path().join("nonexistent.geo");
let result = GeometricStorage::open(&geo_path);
assert!(result.is_err(), "Should fail for nonexistent file");
}
#[test]
fn test_geometric_storage_open_wrong_extension() {
let temp_dir = TempDir::new().unwrap();
let wrong_path = temp_dir.path().join("test.db");
std::fs::write(&wrong_path, b"dummy").unwrap();
let result = GeometricStorage::open(&wrong_path);
assert!(result.is_err(), "Should fail for non-.geo file");
}
#[test]
fn test_geometric_storage_create_and_open() {
let temp_dir = TempDir::new().unwrap();
let geo_path = temp_dir.path().join("new.geo");
let result = GeometricStorage::create(&geo_path);
assert!(result.is_ok(), "Should create new .geo file");
let result = GeometricStorage::open(&geo_path);
assert!(result.is_ok(), "Should open created .geo file");
}
#[test]
fn test_geometric_storage_find_symbols_empty() {
let (_temp_dir, geo_path) = create_test_geo_db();
let storage = GeometricStorage::open(&geo_path).unwrap();
let symbols = storage.find_symbols_by_name("nonexistent");
assert!(
symbols.is_empty(),
"Should return empty for nonexistent symbol"
);
}
#[test]
fn test_geometric_storage_complete_prefix_empty() {
let (_temp_dir, geo_path) = create_test_geo_db();
let storage = GeometricStorage::open(&geo_path).unwrap();
let completions = storage.complete_fqn_prefix("test", 10);
assert!(
completions.is_empty(),
"Should return empty for empty database"
);
}
}