use super::graph::RelatedChunk;
use crate::db::sqlite::graph::ImportDirection;
use crate::db::traits::StoreChunks;
use crate::db::traits::StoreGraph;
use crate::db::SqliteStore;
use anyhow::{Context as AnyhowContext, Result};
pub async fn find_test_files(store: &SqliteStore, chunk_id: i64) -> Result<Vec<RelatedChunk>> {
store
.run(move |conn| {
let mut stmt = conn.prepare(
"SELECT DISTINCT
c.id,
f.relpath,
c.symbol_name,
c.kind,
c.start_line,
c.end_line,
c.preview,
0 as depth,
1.0 as relevance
FROM chunk_edges e
JOIN chunks c ON c.id = e.src_chunk_id
JOIN files f ON f.id = c.file_id
WHERE e.dst_chunk_id = ?1 AND e.type = 'test_of'
ORDER BY relevance DESC",
)?;
let rows = stmt.query_map(rusqlite::params![chunk_id], |row| {
Ok(RelatedChunk {
id: row.get(0)?,
relpath: row.get(1)?,
symbol_name: row.get(2)?,
kind: row.get(3)?,
start_line: row.get(4)?,
end_line: row.get(5)?,
preview: row.get(6)?,
depth: row.get(7)?,
relevance: row.get(8)?,
})
})?;
let mut tests = Vec::new();
for test_result in rows {
tests.push(test_result?);
}
Ok(tests)
})
.await
.context("Failed to find test files")
}
pub async fn find_callers(
store: &SqliteStore,
chunk_id: i64,
max_depth: i32,
) -> Result<Vec<RelatedChunk>> {
let graph_results = store
.find_callers(chunk_id, Some(max_depth as usize))
.await?;
let mut related_chunks = Vec::new();
for result in graph_results {
if let Some(chunk) = store.get_chunk_by_id(result.chunk_id).await? {
related_chunks.push(RelatedChunk {
id: chunk.id,
relpath: chunk.file_path,
symbol_name: chunk.symbol_name,
kind: chunk.kind,
start_line: chunk.start_line,
end_line: chunk.end_line,
preview: chunk.preview,
depth: result.depth as i32,
relevance: 0.7_f64.powi(result.depth as i32), });
}
}
Ok(related_chunks)
}
pub async fn find_callees(
store: &SqliteStore,
chunk_id: i64,
max_depth: i32,
) -> Result<Vec<RelatedChunk>> {
let graph_results = store
.find_callees(chunk_id, Some(max_depth as usize))
.await?;
let mut related_chunks = Vec::new();
for result in graph_results {
if let Some(chunk) = store.get_chunk_by_id(result.chunk_id).await? {
related_chunks.push(RelatedChunk {
id: chunk.id,
relpath: chunk.file_path,
symbol_name: chunk.symbol_name,
kind: chunk.kind,
start_line: chunk.start_line,
end_line: chunk.end_line,
preview: chunk.preview,
depth: result.depth as i32,
relevance: 0.7_f64.powi(result.depth as i32), });
}
}
Ok(related_chunks)
}
pub async fn find_imports(store: &SqliteStore, chunk_id: i64) -> Result<Vec<RelatedChunk>> {
let graph_results = store
.find_imports(chunk_id, ImportDirection::Outgoing, Some(1))
.await?;
let mut related_chunks = Vec::new();
for result in graph_results {
if let Some(chunk) = store.get_chunk_by_id(result.chunk_id).await? {
related_chunks.push(RelatedChunk {
id: chunk.id,
relpath: chunk.file_path,
symbol_name: chunk.symbol_name,
kind: chunk.kind,
start_line: chunk.start_line,
end_line: chunk.end_line,
preview: chunk.preview,
depth: result.depth as i32,
relevance: 1.0, });
}
}
Ok(related_chunks)
}
pub async fn find_exports(store: &SqliteStore, chunk_id: i64) -> Result<Vec<RelatedChunk>> {
store
.run(move |conn| {
let mut stmt = conn.prepare(
"SELECT DISTINCT
c.id,
f.relpath,
c.symbol_name,
c.kind,
c.start_line,
c.end_line,
c.preview
FROM chunk_edges e
JOIN chunks c ON c.id = e.src_chunk_id
JOIN files f ON f.id = c.file_id
WHERE e.dst_chunk_id = ?1 AND e.type = 'exports'",
)?;
let rows = stmt.query_map(rusqlite::params![chunk_id], |row| {
Ok(RelatedChunk {
id: row.get(0)?,
relpath: row.get(1)?,
symbol_name: row.get(2)?,
kind: row.get(3)?,
start_line: row.get(4)?,
end_line: row.get(5)?,
preview: row.get(6)?,
depth: 1,
relevance: 1.0,
})
})?;
let mut exports = Vec::new();
for export_result in rows {
exports.push(export_result?);
}
Ok(exports)
})
.await
.context("Failed to find exports")
}
pub async fn find_routes(store: &SqliteStore, chunk_id: i64) -> Result<Vec<RelatedChunk>> {
store
.run(move |conn| {
let mut stmt = conn.prepare(
"SELECT DISTINCT
c.id,
f.relpath,
c.symbol_name,
c.kind,
c.start_line,
c.end_line,
c.preview
FROM chunk_edges e
JOIN chunks c ON c.id = e.src_chunk_id
JOIN files f ON f.id = c.file_id
WHERE e.dst_chunk_id = ?1 AND e.type = 'route_of'",
)?;
let rows = stmt.query_map(rusqlite::params![chunk_id], |row| {
Ok(RelatedChunk {
id: row.get(0)?,
relpath: row.get(1)?,
symbol_name: row.get(2)?,
kind: row.get(3)?,
start_line: row.get(4)?,
end_line: row.get(5)?,
preview: row.get(6)?,
depth: 1,
relevance: 1.0,
})
})?;
let mut routes = Vec::new();
for route_result in rows {
routes.push(route_result?);
}
Ok(routes)
})
.await
.context("Failed to find routes")
}
pub async fn find_all_relationships(
store: &SqliteStore,
chunk_id: i64,
max_depth: i32,
) -> Result<(
Vec<RelatedChunk>, // tests
Vec<RelatedChunk>, // callers
Vec<RelatedChunk>, // callees
Vec<RelatedChunk>, // imports
Vec<RelatedChunk>, // exports
Vec<RelatedChunk>, // routes
)> {
let (tests, callers, callees, imports, exports, routes) = tokio::try_join!(
find_test_files(store, chunk_id),
find_callers(store, chunk_id, max_depth),
find_callees(store, chunk_id, max_depth),
find_imports(store, chunk_id),
find_exports(store, chunk_id),
find_routes(store, chunk_id),
)?;
Ok((tests, callers, callees, imports, exports, routes))
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[tokio::test]
#[ignore] async fn test_find_test_files() {
}
#[tokio::test]
#[ignore]
async fn test_find_callers() {
}
#[tokio::test]
#[ignore]
async fn test_find_callees() {
}
}