use anyhow::{Context, Result};
use neo4rs::query;
use tracing::warn;
use super::GraphDb;
#[expect(
async_fn_in_trait,
reason = "async trait method is required for the db interfaces"
)]
pub trait DeleteExt {
async fn delete_by_repo(&self, repo_name: &str) -> Result<()>;
async fn delete_by_file_paths(&self, repo_name: &str, file_paths: &[String]) -> Result<()>;
async fn delete_repository(&self, repo_name: &str) -> Result<()>;
}
impl DeleteExt for GraphDb {
async fn delete_by_repo(&self, repo_name: &str) -> Result<()> {
warn!(
"Deleting existing graph nodes for repo '{}' from Neo4j",
repo_name
);
self.graph
.run(
query(
"MATCH (e:Entity)
WHERE e.repo_name = $repo_name
DETACH DELETE e",
)
.param("repo_name", repo_name),
)
.await
.context("Failed to delete existing Neo4j nodes")?;
Ok(())
}
async fn delete_by_file_paths(&self, repo_name: &str, file_paths: &[String]) -> Result<()> {
if file_paths.is_empty() {
return Ok(());
}
warn!(
"Deleting {} file(s) from repo '{}' in Neo4j (incremental mode)",
file_paths.len(),
repo_name
);
self.graph
.run(
query(
"MATCH (e:Entity)
WHERE e.repo_name = $repo_name AND e.file_path IN $file_paths
DETACH DELETE e",
)
.param("repo_name", repo_name)
.param("file_paths", file_paths),
)
.await
.context("Failed to delete Neo4j nodes by file paths")?;
Ok(())
}
async fn delete_repository(&self, repo_name: &str) -> Result<()> {
warn!(
"Deleting :Repository node for repo '{}' from Neo4j",
repo_name
);
self.graph
.run(
query(
"MATCH (r:Repository {name: $repo_name})
DETACH DELETE r",
)
.param("repo_name", repo_name),
)
.await
.context("Failed to delete :Repository node")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::super::GraphDb;
use super::DeleteExt;
use crate::db::graph::connection::ConnectExt;
#[ignore = "requires local Neo4j instance running on bolt://localhost:7687"]
#[tokio::test]
async fn test_graph_db_delete_by_repo() {
let graph_db = GraphDb::connect("bolt://localhost:7687", "neo4j", "password")
.await
.expect("Failed to connect to Neo4j");
let result = graph_db.delete_by_repo("nonexistent-test-repo").await;
assert!(result.is_ok());
}
#[ignore = "requires local Neo4j instance running on bolt://localhost:7687"]
#[tokio::test]
async fn test_graph_db_delete_by_file_paths() {
let graph_db = GraphDb::connect("bolt://localhost:7687", "neo4j", "password")
.await
.expect("Failed to connect to Neo4j");
let file_paths = vec![
"/test/path/File1.java".to_string(),
"/test/path/File2.java".to_string(),
];
let result = graph_db
.delete_by_file_paths("nonexistent-test-repo", &file_paths)
.await;
assert!(result.is_ok());
}
#[ignore = "requires local Neo4j instance running on bolt://localhost:7687"]
#[tokio::test]
async fn test_graph_db_delete_by_file_paths_empty() {
let graph_db = GraphDb::connect("bolt://localhost:7687", "neo4j", "password")
.await
.expect("Failed to connect to Neo4j");
let result = graph_db.delete_by_file_paths("test-repo", &[]).await;
assert!(result.is_ok());
}
}