use async_trait::async_trait;
use sqlx::Row;
use super::PostgresStore;
use crate::db::traits::StoreCleanup;
use crate::db::{StaleWorktree, WorktreeCleanupResult};
#[async_trait]
impl StoreCleanup for PostgresStore {
async fn detect_stale_worktrees(&self) -> anyhow::Result<Vec<StaleWorktree>> {
let rows = sqlx::query("SELECT id, repo_id, name, abs_path FROM worktrees ORDER BY id")
.fetch_all(&self.pool)
.await?;
let mut stale = Vec::new();
for r in rows {
let abs_path: String = r.get("abs_path");
let exists = match tokio::fs::try_exists(&abs_path).await {
Ok(b) => b,
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => true,
Err(_) => false,
};
if exists {
continue;
}
let id: i64 = r.get("id");
let chunk_count: i64 =
sqlx::query_scalar("SELECT count(*) FROM chunk_worktrees WHERE worktree_id = $1")
.bind(id)
.fetch_one(&self.pool)
.await?;
stale.push(StaleWorktree {
id,
repo_id: r.get("repo_id"),
name: r.get("name"),
abs_path,
exists: false,
chunk_count,
});
}
Ok(stale)
}
async fn delete_worktree_data(
&self,
worktree_id: i64,
) -> anyhow::Result<WorktreeCleanupResult> {
sqlx::query("DELETE FROM chunk_worktrees WHERE worktree_id = $1")
.bind(worktree_id)
.execute(&self.pool)
.await?;
let chunks_deleted = sqlx::query(
"DELETE FROM chunks WHERE NOT EXISTS \
(SELECT 1 FROM chunk_worktrees cw WHERE cw.chunk_id = chunks.id)",
)
.execute(&self.pool)
.await?
.rows_affected();
let files_deleted = sqlx::query(
"DELETE FROM files WHERE worktree_id = $1 \
AND NOT EXISTS (SELECT 1 FROM chunks c WHERE c.file_id = files.id)",
)
.bind(worktree_id)
.execute(&self.pool)
.await?
.rows_affected();
sqlx::query("DELETE FROM worktrees WHERE id = $1")
.bind(worktree_id)
.execute(&self.pool)
.await?;
Ok(WorktreeCleanupResult {
chunks_deleted,
files_deleted,
embeddings_deleted: 0,
})
}
}