use anyhow::Result;
use std::path::{Path, PathBuf};
use crate::graph::schema::GeoIndexMeta;
pub fn is_geo_stale(db_path: &Path, geo_path: &Path) -> Result<bool> {
if !geo_path.exists() {
return Ok(true);
}
let db_modified = std::fs::metadata(db_path)?.modified()?;
let geo_modified = std::fs::metadata(geo_path)?.modified()?;
if db_modified > geo_modified {
return Ok(true);
}
let conn = rusqlite::Connection::open(db_path)?;
let meta = match GeoIndexMeta::get_geo_index_meta(&conn)? {
Some(m) => m,
None => return Ok(true), };
let current_schema = crate::migrate_cmd::MAGELLAN_SCHEMA_VERSION;
if meta.schema_version != current_schema {
return Ok(true);
}
let (symbol_count, call_count, cfg_block_count) = get_current_counts(&conn)?;
if meta.symbol_count != symbol_count
|| meta.call_count != call_count
|| meta.cfg_block_count != cfg_block_count
{
return Ok(true);
}
Ok(false)
}
#[cfg(feature = "geometric-backend")]
pub fn ensure_geo_index(db_path: &Path) -> Result<PathBuf> {
let geo_path = db_path.with_extension("geo");
if is_geo_stale(db_path, &geo_path)? {
eprintln!("Building geometric index from SQLite...");
let stats = crate::geo_builder::build_geo_index(db_path, &geo_path)?;
eprintln!(
"Geometric index built: {} symbols, {} calls",
stats.symbol_count, stats.call_count
);
}
Ok(geo_path)
}
#[cfg(not(feature = "geometric-backend"))]
pub fn ensure_geo_index(_db_path: &Path) -> Result<PathBuf> {
anyhow::bail!(
"Geometric index requires the 'geometric-backend' feature. \
Install with: cargo install magellan --features geometric-backend"
)
}
#[cfg(feature = "geometric-backend")]
pub fn rebuild_geo_index(db_path: &Path) -> Result<PathBuf> {
let geo_path = db_path.with_extension("geo");
if geo_path.exists() {
std::fs::remove_file(&geo_path)?;
}
ensure_geo_index(db_path)
}
#[cfg(not(feature = "geometric-backend"))]
pub fn rebuild_geo_index(db_path: &Path) -> Result<PathBuf> {
let _ = db_path;
anyhow::bail!(
"Geometric index requires the 'geometric-backend' feature. \
Install with: cargo install magellan --features geometric-backend"
)
}
fn get_current_counts(conn: &rusqlite::Connection) -> Result<(i64, i64, i64)> {
let symbol_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM graph_entities WHERE kind = 'Symbol'",
[],
|row| row.get(0),
)
.unwrap_or(0);
let call_count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM graph_entities WHERE kind = 'Call'",
[],
|row| row.get(0),
)
.unwrap_or(0);
let cfg_block_count: i64 = conn
.query_row("SELECT COUNT(*) FROM cfg_blocks", [], |row| row.get(0))
.unwrap_or(0);
Ok((symbol_count, call_count, cfg_block_count))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_geo_staleness_missing_file() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let geo_path = temp_dir.path().join("test.geo");
let _ = crate::CodeGraph::open(&db_path).unwrap();
assert!(is_geo_stale(&db_path, &geo_path).unwrap());
}
#[test]
fn test_geo_staleness_fresh_build() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let geo_path = temp_dir.path().join("test.geo");
let _ = crate::CodeGraph::open(&db_path).unwrap();
std::fs::write(&geo_path, b"geo").unwrap();
assert!(is_geo_stale(&db_path, &geo_path).unwrap());
}
}