use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use crate::graph::CodeGraph;
use crate::graph::SymbolNode;
use crate::ingest::SymbolKind;
use sqlitegraph::{GraphBackend, SnapshotId};
#[derive(Debug, Clone)]
pub struct UnifiedSymbolInfo {
pub id: u64,
pub name: String,
pub fqn: String,
pub kind: SymbolKind,
pub file_path: String,
pub byte_start: u64,
pub byte_end: u64,
pub start_line: u64,
pub start_col: u64,
pub end_line: u64,
pub end_col: u64,
pub language: Option<String>,
}
#[derive(Debug, PartialEq)]
pub enum BackendType {
SQLite,
}
pub enum MagellanBackend {
SQLite(CodeGraph),
}
impl MagellanBackend {
pub fn detect_type(_db_path: &Path) -> BackendType {
BackendType::SQLite
}
pub fn create(db_path: &Path) -> Result<Self> {
let graph = CodeGraph::open(db_path).context("Failed to create SQLite database")?;
Ok(MagellanBackend::SQLite(graph))
}
pub fn open_or_create(db_path: &Path) -> Result<Self> {
if db_path.exists() {
Self::open(db_path)
} else {
Self::create(db_path)
}
}
pub fn open(db_path: &Path) -> Result<Self> {
let graph = CodeGraph::open(db_path).context("Failed to open SQLite database")?;
Ok(MagellanBackend::SQLite(graph))
}
pub fn find_symbol_by_fqn(&self, fqn: &str) -> Result<Option<UnifiedSymbolInfo>> {
match self {
MagellanBackend::SQLite(graph) => {
if let Ok(id) = fqn.parse::<i64>() {
let snapshot = SnapshotId::current();
if let Ok(node) = graph.backend().get_node(snapshot, id) {
if node.kind == "Symbol" {
if let Ok(symbol_node) = serde_json::from_value::<SymbolNode>(node.data)
{
return Ok(Some(Self::convert_symbol_node(&symbol_node, id)));
}
}
}
}
let symbols = Self::get_all_sqlite_symbols(graph)?;
for (entity_id, symbol) in symbols {
if symbol.fqn.as_deref() == Some(fqn) {
return Ok(Some(Self::convert_symbol_node(&symbol, entity_id)));
}
}
Ok(None)
}
}
}
pub fn find_symbol_by_id(&self, id: u64) -> Option<UnifiedSymbolInfo> {
match self {
MagellanBackend::SQLite(graph) => {
let snapshot = SnapshotId::current();
graph
.backend()
.get_node(snapshot, id as i64)
.ok()
.and_then(|node| {
if node.kind == "Symbol" {
serde_json::from_value::<SymbolNode>(node.data)
.ok()
.map(|symbol| Self::convert_symbol_node(&symbol, id as i64))
} else {
None
}
})
}
}
}
pub fn find_symbols_by_name(&self, name: &str) -> Result<Vec<UnifiedSymbolInfo>> {
match self {
MagellanBackend::SQLite(graph) => {
let symbols = Self::get_all_sqlite_symbols(graph)?;
let results: Vec<UnifiedSymbolInfo> = symbols
.into_iter()
.filter(|(_, symbol)| symbol.name.as_deref() == Some(name))
.map(|(entity_id, symbol)| Self::convert_symbol_node(&symbol, entity_id))
.collect();
Ok(results)
}
}
}
pub fn get_stats(&self) -> Result<BackendStats> {
match self {
MagellanBackend::SQLite(graph) => {
let symbol_count = graph.count_symbols().unwrap_or(0);
let file_count = graph.count_files().unwrap_or(0);
let cfg_block_count = 0;
Ok(BackendStats {
node_count: symbol_count,
symbol_count,
file_count,
cfg_block_count,
})
}
}
}
fn convert_symbol_node(node: &SymbolNode, entity_id: i64) -> UnifiedSymbolInfo {
UnifiedSymbolInfo {
id: entity_id as u64,
name: node.name.clone().unwrap_or_default(),
fqn: node.fqn.clone().unwrap_or_default(),
kind: SymbolKind::parse(&node.kind).unwrap_or(SymbolKind::Unknown),
file_path: String::new(),
byte_start: node.byte_start as u64,
byte_end: node.byte_end as u64,
start_line: node.start_line as u64,
start_col: node.start_col as u64,
end_line: node.end_line as u64,
end_col: node.end_col as u64,
language: None,
}
}
fn get_all_sqlite_symbols(graph: &CodeGraph) -> Result<Vec<(i64, SymbolNode)>> {
let backend = graph.backend();
let entity_ids = backend.entity_ids()?;
let snapshot = SnapshotId::current();
let mut symbols = Vec::new();
for entity_id in entity_ids {
if let Ok(node) = backend.get_node(snapshot, entity_id) {
if node.kind == "Symbol" {
if let Ok(symbol_node) = serde_json::from_value::<SymbolNode>(node.data) {
symbols.push((entity_id, symbol_node));
}
}
}
}
Ok(symbols)
}
pub fn export_json(&self) -> Result<String> {
match self {
MagellanBackend::SQLite(_graph) => Err(anyhow::anyhow!(
"export_json not implemented for SQLite backend"
)),
}
}
pub fn export_jsonl(&self) -> Result<String> {
match self {
MagellanBackend::SQLite(_graph) => Err(anyhow::anyhow!(
"export_jsonl not implemented for SQLite backend"
)),
}
}
pub fn export_csv(&self) -> Result<String> {
match self {
MagellanBackend::SQLite(_graph) => Err(anyhow::anyhow!(
"export_csv not implemented for SQLite backend"
)),
}
}
pub fn symbols_in_file(&self, file_path: &str) -> Result<Vec<UnifiedSymbolInfo>> {
match self {
MagellanBackend::SQLite(_graph) => {
let _ = file_path;
Ok(Vec::new())
}
}
}
pub fn get_code_chunks(
&self,
file_path: &str,
) -> Result<Vec<crate::generation::schema::CodeChunk>> {
match self {
MagellanBackend::SQLite(graph) => graph.get_code_chunks(file_path),
}
}
pub fn get_code_chunks_for_symbol(
&self,
file_path: &str,
symbol_name: &str,
) -> Result<Vec<crate::generation::schema::CodeChunk>> {
match self {
MagellanBackend::SQLite(graph) => {
graph.get_code_chunks_for_symbol(file_path, symbol_name)
}
}
}
pub fn get_code_chunk_by_span(
&self,
file_path: &str,
byte_start: usize,
byte_end: usize,
) -> Result<Option<crate::generation::schema::CodeChunk>> {
match self {
MagellanBackend::SQLite(graph) => {
graph.get_code_chunk_by_span(file_path, byte_start, byte_end)
}
}
}
pub fn start_execution(
&self,
exec_id: &str,
version: &str,
args: &[String],
root: Option<&str>,
db_path: &str,
) -> Result<i64> {
match self {
MagellanBackend::SQLite(graph) => graph
.execution_log()
.start_execution(exec_id, version, args, root, db_path),
}
}
pub fn finish_execution(
&self,
exec_id: &str,
status: &str,
error: Option<&str>,
files_processed: usize,
symbols_indexed: usize,
refs_indexed: usize,
) -> Result<()> {
match self {
MagellanBackend::SQLite(graph) => graph.execution_log().finish_execution(
exec_id,
status,
error,
files_processed,
symbols_indexed,
refs_indexed,
),
}
}
pub fn dead_code_from_entries(&self, _entry_ids: &[u64]) -> Vec<u64> {
Vec::new()
}
pub fn get_sccs(&self) -> SccResult {
SccResult {
sccs: Vec::new(),
scc_count: 0,
}
}
pub fn condense_graph(&self) -> CondensationDag {
CondensationDag {
dag: Vec::new(),
node_count: 0,
}
}
pub fn enumerate_paths(&self, _entry_id: u64, _max_depth: usize) -> PathEnumerationResult {
PathEnumerationResult {
paths: Vec::new(),
path_count: 0,
}
}
pub fn reverse_reachable_from(&self, _id: u64) -> Vec<u64> {
Vec::new()
}
pub fn reachable_from(&self, _id: u64) -> Vec<u64> {
Vec::new()
}
}
#[derive(Debug)]
pub struct BackendStats {
pub node_count: usize,
pub symbol_count: usize,
pub file_count: usize,
pub cfg_block_count: usize,
}
#[derive(Debug)]
pub struct SccResult {
pub sccs: Vec<Vec<u64>>,
pub scc_count: usize,
}
#[derive(Debug)]
pub struct CondensationDag {
pub dag: Vec<(u64, u64)>,
pub node_count: usize,
}
#[derive(Debug)]
pub struct PathEnumerationResult {
pub paths: Vec<Vec<u64>>,
pub path_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_type_sqlite() {
let temp = tempfile::tempdir().unwrap();
let db_path = temp.path().join("test.db");
std::fs::File::create(&db_path).unwrap();
assert_eq!(MagellanBackend::detect_type(&db_path), BackendType::SQLite);
}
}