use anyhow::Result;
use rusqlite::params;
use super::MetricsOps;
#[derive(Debug, Clone)]
pub struct BackfillResult {
pub total: usize,
pub processed: usize,
pub errors: Vec<(String, String)>,
}
impl MetricsOps {
pub fn backfill_all_metrics(
&self,
progress: Option<&(dyn Fn(usize, usize, &str) + Send + Sync)>,
) -> Result<BackfillResult> {
let conn = self.connect()?;
let mut stmt = conn.prepare(
"SELECT DISTINCT json_extract(data, '$.file_path') as file_path
FROM graph_entities
WHERE kind = 'Symbol'
AND json_extract(data, '$.file_path') IS NOT NULL
ORDER BY file_path",
)?;
let files: Vec<String> = stmt
.query_map([], |row| {
let file_path: String = row.get(0)?;
Ok(file_path)
})?
.collect::<Result<Vec<_>, _>>()?;
drop(stmt);
drop(conn);
let total = files.len();
let mut processed = 0;
let mut errors = Vec::new();
for file_path in files {
let source = match std::fs::read(&file_path) {
Ok(s) => s,
Err(e) => {
errors.push((file_path.clone(), format!("Read error: {}", e)));
continue;
}
};
let symbols = match self.get_file_symbols(&file_path) {
Ok(s) => s,
Err(e) => {
errors.push((file_path.clone(), format!("Symbol query error: {}", e)));
continue;
}
};
if let Err(e) = self.compute_for_file(&file_path, &source, &symbols) {
errors.push((file_path.clone(), format!("Compute error: {}", e)));
}
processed += 1;
if let Some(cb) = progress {
cb(processed, total, &file_path);
}
}
Ok(BackfillResult {
total,
processed,
errors,
})
}
fn get_file_symbols(&self, file_path: &str) -> Result<Vec<crate::graph::schema::SymbolNode>> {
let conn = self.connect()?;
let mut stmt = conn.prepare(
"SELECT data
FROM graph_entities
WHERE kind = 'Symbol'
AND json_extract(data, '$.file_path') = ?1",
)?;
let symbols = stmt
.query_map(params![file_path], |row| {
let data: String = row.get(0)?;
let symbol: crate::graph::schema::SymbolNode = serde_json::from_str(&data)
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
Ok(symbol)
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(symbols)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backfill_result_structure() {
let result = BackfillResult {
total: 10,
processed: 9,
errors: vec![("test.rs".to_string(), "Read error".to_string())],
};
assert_eq!(result.total, 10);
assert_eq!(result.processed, 9);
assert_eq!(result.errors.len(), 1);
assert_eq!(result.errors[0].0, "test.rs");
assert_eq!(result.errors[0].1, "Read error");
}
}