use anyhow::{Context, Result};
use crate::core::classifier::QueryIntent;
use crate::core::entity::EdgeKind;
use super::super::{hash_query, CodeIndexer};
impl CodeIndexer {
pub(super) async fn fetch_chunks_for_ids(
&self,
ids: &[String],
) -> std::collections::HashMap<String, crate::core::chunker::RawChunk> {
if ids.is_empty() {
return std::collections::HashMap::new();
}
if let Some(corpus) = self.corpus.clone() {
let owned_ids = ids.to_vec();
let index_id = self.index_id.clone();
let read = tokio::task::spawn_blocking(move || {
let refs: Vec<&str> = owned_ids.iter().map(String::as_str).collect();
corpus.get_chunks(&refs)
})
.await;
match read {
Ok(Ok(chunks)) => {
return chunks.into_iter().map(|c| (c.id.clone(), c)).collect();
}
Ok(Err(e)) => tracing::warn!(
"index '{index_id}': redb point-read failed ({e}) — \
falling back to in-memory corpus for this query"
),
Err(e) => tracing::warn!(
"index '{index_id}': redb point-read task panicked ({e}) — \
falling back to in-memory corpus for this query"
),
}
}
self.ensure_chunks_loaded().await;
let chunks = self.chunks.read().await;
ids.iter()
.filter_map(|id| chunks.get(id).map(|c| (id.clone(), c.clone())))
.collect()
}
pub fn get_embedding(&self, chunk_id: &str) -> Option<Vec<f32>> {
self.chunk_embeddings
.try_read()
.ok()
.and_then(|g| g.peek(chunk_id).cloned())
}
pub async fn embed_text(&self, text: &str) -> Result<Option<Vec<f32>>> {
let Some(embedder) = self.embedder.clone() else {
return Ok(None);
};
let vec = embedder.embed(text).await.context("embed text")?;
Ok(Some(vec))
}
pub(crate) async fn embed_query(&self, query: &str) -> Result<Option<Vec<f32>>> {
let Some(embedder) = self.embedder.clone() else {
return Ok(None);
};
let key = hash_query(query);
if let Some(v) = self
.query_cache
.lock()
.expect("query_cache mutex poisoned")
.get(&key)
{
return Ok(Some(v.clone()));
}
let vec = embedder.embed(query).await.context("embed query")?;
self.query_cache
.lock()
.expect("query_cache mutex poisoned")
.put(key, vec.clone());
Ok(Some(vec))
}
pub(super) async fn bm25_search(&self, query: &str, want: usize) -> Result<Vec<(String, f32)>> {
let bm25 = self.bm25.read().await;
if bm25.is_empty() {
return Ok(Vec::new());
}
Ok(bm25.score_query_all(query, want))
}
pub(crate) async fn grep_fallback_search(
&self,
query: &str,
want: usize,
) -> Vec<(String, f32)> {
if query.is_empty() || want == 0 {
return Vec::new();
}
let Ok(re) = regex::Regex::new(®ex::escape(query)) else {
return Vec::new();
};
self.ensure_chunks_loaded().await;
let chunks = self.chunks.read().await;
let mut out: Vec<(String, f32)> = Vec::new();
for raw in chunks.values() {
if re.is_match(&raw.content) {
out.push((raw.id.clone(), super::GREP_FALLBACK_SCORE));
if out.len() >= want {
break;
}
}
}
out
}
pub(crate) async fn vector_search(
&self,
embedding: &[f32],
want: usize,
) -> Result<Vec<(String, f32)>> {
let Some(store) = &self.store else {
return Ok(Vec::new());
};
let hits = store.search(embedding, want).await?;
Ok(hits.into_iter().map(|h| (h.chunk_id, h.score)).collect())
}
pub(super) fn edge_kinds_for_intent(intent: QueryIntent) -> Vec<EdgeKind> {
match intent {
QueryIntent::Definition => {
vec![EdgeKind::Implements, EdgeKind::Aliases, EdgeKind::UsesType]
}
QueryIntent::Usage => vec![
EdgeKind::CallsFunction,
EdgeKind::CalledByFunction,
EdgeKind::TestedBy,
EdgeKind::CoOccursInTest,
],
QueryIntent::Conceptual => {
vec![EdgeKind::ReferencesConcept, EdgeKind::Documents]
}
QueryIntent::BugDebt => vec![
EdgeKind::RaisesError,
EdgeKind::ErrorDescribes,
EdgeKind::Configures,
],
QueryIntent::Unknown => vec![EdgeKind::CallsFunction, EdgeKind::CalledByFunction],
}
}
}