use std::collections::{HashMap, HashSet};
use async_trait::async_trait;
use crate::config::EdgeQualityWeights;
use crate::db::index_state::UpdateStats;
use crate::db::types::{
ChunkMetadata, EmbeddingRecord, EncodingRunRow, GraphResult, HybridResult, HybridWeights,
ImportDirection, RankedSearchHit, SemanticRanking,
};
use crate::db::{
ChunkContext, ChunkForEmbedding, ChunkFull, ChunkRecord, ChunkSummary, FileRecord, RepoInfo,
SearchHit, StaleWorktree, WorktreeCleanupResult, WorktreeInfo,
};
#[async_trait]
pub trait StoreCore: Send + Sync {
fn has_vector_extension(&self) -> bool;
async fn get_or_create_repo(&self, name: &str, root_path: &str) -> anyhow::Result<i64>;
async fn get_or_create_worktree(
&self,
repo_id: i64,
name: &str,
abs_path: &str,
) -> anyhow::Result<i64>;
async fn get_or_create_commit(
&self,
repo_id: i64,
sha: &str,
committed_at: Option<chrono::DateTime<chrono::Utc>>,
) -> anyhow::Result<i64>;
async fn get_repo_by_name(&self, name: &str) -> anyhow::Result<Option<RepoInfo>>;
async fn get_worktree_by_name(
&self,
repo_id: i64,
name: &str,
) -> anyhow::Result<Option<WorktreeInfo>>;
async fn list_repos(&self) -> anyhow::Result<Vec<RepoInfo>>;
async fn list_worktrees(&self, repo_id: i64) -> anyhow::Result<Vec<WorktreeInfo>>;
async fn upsert_file(&self, file: &FileRecord) -> anyhow::Result<i64>;
async fn delete_file(&self, file_id: i64) -> anyhow::Result<bool>;
async fn get_file_id_by_relpath(
&self,
relpath: &str,
worktree_id: i64,
) -> anyhow::Result<Option<i64>>;
async fn get_worktree_chunk_count(&self, worktree_id: i64) -> anyhow::Result<i64>;
async fn get_worktree_file_count(&self, worktree_id: i64) -> anyhow::Result<i64>;
async fn get_worktree_embedding_count(&self, worktree_id: i64) -> anyhow::Result<i64>;
async fn get_worktree_language_breakdown(
&self,
worktree_id: i64,
) -> anyhow::Result<Vec<(String, i64)>>;
async fn get_worktree_last_scan(&self, worktree_id: i64) -> anyhow::Result<Option<String>>;
async fn get_global_chunk_count(&self) -> anyhow::Result<i64>;
async fn get_global_embedding_count(&self) -> anyhow::Result<i64>;
async fn get_repo_chunk_count(&self, repo_name: &str) -> anyhow::Result<i64>;
async fn get_repo_embedding_count(&self, repo_name: &str) -> anyhow::Result<i64>;
}
#[async_trait]
pub trait StoreChunks: Send + Sync {
async fn insert_chunk(&self, chunk: &ChunkRecord) -> anyhow::Result<i64>;
async fn insert_chunks_batch(&self, chunks: &[ChunkRecord]) -> anyhow::Result<Vec<i64>>;
async fn insert_chunk_edge(
&self,
src_chunk_id: i64,
dst_chunk_id: i64,
edge_type: &str,
) -> anyhow::Result<()>;
async fn get_chunk_by_id(&self, chunk_id: i64) -> anyhow::Result<Option<ChunkFull>>;
async fn get_file_chunks(&self, file_id: i64) -> anyhow::Result<Vec<ChunkSummary>>;
async fn get_chunk_context(
&self,
chunk_id: i64,
surrounding: usize,
) -> anyhow::Result<Option<ChunkContext>>;
async fn find_chunk_by_symbol(
&self,
repo_id: i64,
worktree_id: Option<i64>,
symbol_name: &str,
relpath: Option<&str>,
) -> anyhow::Result<Option<i64>>;
async fn delete_chunks_by_file(&self, file_id: i64) -> anyhow::Result<u64>;
async fn delete_chunks_by_ids(
&self,
worktree_id: i64,
chunk_ids: &[i64],
) -> anyhow::Result<usize>;
async fn get_chunks_for_worktree(&self, worktree_id: i64)
-> anyhow::Result<Vec<(i64, String)>>;
async fn get_chunks_by_blob_sha(&self, blob_sha: &str) -> anyhow::Result<Vec<ChunkSummary>>;
async fn add_chunk_to_worktree(&self, chunk_id: i64, worktree_id: i64) -> anyhow::Result<()>;
async fn get_chunk_worktrees(&self, chunk_id: i64) -> anyhow::Result<Vec<i64>>;
}
#[allow(clippy::too_many_arguments)] #[async_trait]
pub trait StoreSearch: Send + Sync {
async fn search_chunks_fts(
&self,
repo: &str,
worktree: Option<&str>,
query: &str,
k: i64,
debug: bool,
kind_filter: Option<&[String]>,
lang_filter: Option<&[String]>,
) -> anyhow::Result<(Vec<SearchHit>, usize)>;
async fn search_fts_by_id(
&self,
repo_id: i64,
worktree_id: Option<i64>,
query: &str,
normalized_query: &str,
k: i64,
) -> anyhow::Result<Vec<SearchHit>>;
async fn search_chunks_vector(
&self,
repo: &str,
worktree: Option<&str>,
embedding: &[f32],
k: i64,
debug: bool,
kind_filter: Option<&[String]>,
lang_filter: Option<&[String]>,
) -> anyhow::Result<Vec<SearchHit>>;
async fn search_vector_by_id(
&self,
repo_id: i64,
worktree_id: Option<i64>,
query_embedding: &[f32],
k: i64,
) -> anyhow::Result<Vec<SearchHit>>;
async fn search_chunks_hybrid(
&self,
repo: &str,
worktree: Option<&str>,
query: &str,
embedding: &[f32],
k: i64,
debug: bool,
kind_filter: Option<&[String]>,
lang_filter: Option<&[String]>,
) -> anyhow::Result<Vec<SearchHit>>;
async fn search_hybrid(
&self,
repo: &str,
worktree: Option<&str>,
query: &str,
query_embedding: &[f32],
limit: usize,
weights: HybridWeights,
) -> anyhow::Result<Vec<HybridResult>>;
async fn search_hybrid_ranked(
&self,
repo: &str,
worktree: Option<&str>,
query: &str,
query_embedding: &[f32],
limit: usize,
weights: HybridWeights,
ranking: SemanticRanking,
) -> anyhow::Result<Vec<RankedSearchHit>>;
async fn get_chunks_metadata(
&self,
chunk_ids: &[i64],
) -> anyhow::Result<HashMap<i64, ChunkMetadata>>;
}
#[async_trait]
pub trait StoreGraph: Send + Sync {
async fn find_callers(
&self,
target_chunk_id: i64,
max_depth: Option<usize>,
) -> anyhow::Result<Vec<GraphResult>>;
async fn find_callees(
&self,
source_chunk_id: i64,
max_depth: Option<usize>,
) -> anyhow::Result<Vec<GraphResult>>;
async fn find_imports(
&self,
chunk_id: i64,
direction: ImportDirection,
max_depth: Option<usize>,
) -> anyhow::Result<Vec<GraphResult>>;
async fn find_extensions(
&self,
chunk_id: i64,
direction: ImportDirection,
max_depth: Option<usize>,
) -> anyhow::Result<Vec<GraphResult>>;
async fn get_direct_edges(
&self,
chunk_id: i64,
direction: ImportDirection,
) -> anyhow::Result<Vec<GraphResult>>;
async fn calculate_graph_importance(
&self,
repo_id: i64,
worktree_id: Option<i64>,
limit: usize,
enable_quality: bool,
weights: &EdgeQualityWeights,
) -> anyhow::Result<Vec<SearchHit>>;
async fn calculate_graph_importance_for_chunks(
&self,
chunk_ids: &[i64],
repo_id: i64,
worktree_id: Option<i64>,
) -> anyhow::Result<Vec<SearchHit>>;
async fn calculate_signal_scores(
&self,
repo_id: i64,
worktree_id: Option<i64>,
recency_weight: f32,
churn_weight: f32,
limit: usize,
) -> anyhow::Result<Vec<SearchHit>>;
async fn calculate_signal_scores_for_chunks(
&self,
chunk_ids: &[i64],
repo_id: i64,
worktree_id: Option<i64>,
recency_weight: f32,
churn_weight: f32,
) -> anyhow::Result<Vec<SearchHit>>;
}
#[async_trait]
pub trait StoreEmbeddings: Send + Sync {
async fn upsert_embedding(
&self,
blob_sha: &str,
embedding: &[f32],
model_version: &str,
) -> anyhow::Result<i64>;
async fn upsert_embeddings_batch_new(
&self,
embeddings: &[EmbeddingRecord],
) -> anyhow::Result<()>;
async fn has_embedding(&self, blob_sha: &str) -> anyhow::Result<bool>;
async fn get_embedding(&self, blob_sha: &str) -> anyhow::Result<Option<Vec<f32>>>;
async fn sync_embedding_to_vec(
&self,
embedding_id: i64,
embedding: &[f32],
) -> anyhow::Result<()>;
async fn sync_all_embeddings_to_vec(&self) -> anyhow::Result<usize>;
async fn get_chunks_needing_embeddings_count(&self) -> anyhow::Result<i64>;
async fn copy_existing_embeddings_from_cache(&self) -> anyhow::Result<i64>;
async fn fetch_chunks_needing_embeddings(
&self,
incremental: bool,
sample_size: Option<usize>,
) -> anyhow::Result<Vec<ChunkForEmbedding>>;
}
#[async_trait]
pub trait StoreMigration: Send + Sync {
async fn migrate(&self) -> anyhow::Result<()>;
async fn get_applied_migrations(&self) -> anyhow::Result<HashSet<i32>>;
}
#[async_trait]
pub trait StoreCleanup: Send + Sync {
async fn detect_stale_worktrees(&self) -> anyhow::Result<Vec<StaleWorktree>>;
async fn delete_worktree_data(&self, worktree_id: i64)
-> anyhow::Result<WorktreeCleanupResult>;
}
#[async_trait]
pub trait StoreIndexState: Send + Sync {
async fn get_last_indexed_tree(&self, worktree_id: i64) -> anyhow::Result<String>;
async fn update_index_state(
&self,
worktree_id: i64,
tree_sha: &str,
stats: &UpdateStats,
) -> anyhow::Result<()>;
}
#[async_trait]
pub trait StoreEncoding: Send + Sync {
async fn create_encoding_run(
&self,
total_chunks: i64,
provider: Option<&str>,
dimension: Option<i32>,
) -> anyhow::Result<i64>;
async fn update_encoding_run_progress(
&self,
run_id: i64,
chunks_completed: i64,
chunks_per_second: Option<f64>,
) -> anyhow::Result<()>;
async fn complete_encoding_run(&self, run_id: i64, status: &str) -> anyhow::Result<()>;
async fn mark_stale_runs_as_failed(&self) -> anyhow::Result<()>;
async fn get_active_encoding_run(&self) -> anyhow::Result<Option<EncodingRunRow>>;
}
pub trait Store:
StoreCore
+ StoreChunks
+ StoreSearch
+ StoreGraph
+ StoreEmbeddings
+ StoreMigration
+ StoreCleanup
+ StoreIndexState
+ StoreEncoding
{
}
impl<T> Store for T where
T: StoreCore
+ StoreChunks
+ StoreSearch
+ StoreGraph
+ StoreEmbeddings
+ StoreMigration
+ StoreCleanup
+ StoreIndexState
+ StoreEncoding
{
}
#[cfg(test)]
mod tests {
use super::*;
fn _assert_object_safe_core(_: &dyn StoreCore) {}
fn _assert_object_safe_chunks(_: &dyn StoreChunks) {}
fn _assert_object_safe_search(_: &dyn StoreSearch) {}
fn _assert_object_safe_graph(_: &dyn StoreGraph) {}
fn _assert_object_safe_embeddings(_: &dyn StoreEmbeddings) {}
fn _assert_object_safe_migration(_: &dyn StoreMigration) {}
fn _assert_object_safe_cleanup(_: &dyn StoreCleanup) {}
fn _assert_object_safe_index_state(_: &dyn StoreIndexState) {}
fn _assert_object_safe_encoding(_: &dyn StoreEncoding) {}
fn _assert_object_safe_store(_: &dyn Store) {}
}