use std::sync::{Arc, Mutex};
use anyhow::Result;
use async_trait::async_trait;
use super::VectorStore;
use crate::embedding::EmbeddingService;
use crate::retrieval::hybrid_top_n;
use crate::types::QuestionSql;
struct Entry {
content: String,
question: Option<String>, embedding: Vec<f32>,
}
pub struct MemoryVectorStore {
embedding: Arc<dyn EmbeddingService>,
n_results: usize,
ddl: Mutex<Vec<Entry>>,
docs: Mutex<Vec<Entry>>,
sql: Mutex<Vec<Entry>>,
}
impl MemoryVectorStore {
pub fn new(embedding: Arc<dyn EmbeddingService>) -> Self {
Self {
embedding,
n_results: 10, ddl: Mutex::new(Vec::new()),
docs: Mutex::new(Vec::new()),
sql: Mutex::new(Vec::new()),
}
}
pub fn with_n_results(mut self, n: usize) -> Self {
self.n_results = n;
self
}
fn top_n(entries: &[Entry], query_text: &str, query_emb: &[f32], n: usize) -> Vec<usize> {
let texts: Vec<&str> = entries
.iter()
.map(|e| e.question.as_deref().unwrap_or(&e.content))
.collect();
let embs: Vec<&[f32]> = entries.iter().map(|e| e.embedding.as_slice()).collect();
hybrid_top_n(query_text, query_emb, &texts, &embs, n)
}
}
#[async_trait]
impl VectorStore for MemoryVectorStore {
async fn add_ddl(&self, ddl: &str) -> Result<()> {
let embedding = self.embedding.embed(ddl).await?;
self.ddl.lock().unwrap().push(Entry {
content: ddl.to_string(),
question: None,
embedding,
});
Ok(())
}
async fn add_documentation(&self, doc: &str) -> Result<()> {
let embedding = self.embedding.embed(doc).await?;
self.docs.lock().unwrap().push(Entry {
content: doc.to_string(),
question: None,
embedding,
});
Ok(())
}
async fn clear_ddl(&self) -> Result<()> {
self.ddl.lock().unwrap().clear();
Ok(())
}
async fn add_question_sql(&self, question: &str, sql: &str) -> Result<()> {
let embedding = self.embedding.embed(question).await?;
self.sql.lock().unwrap().push(Entry {
content: sql.to_string(),
question: Some(question.to_string()),
embedding,
});
Ok(())
}
async fn get_related_ddl(&self, question: &str) -> Result<Vec<String>> {
let query = self.embedding.embed(question).await?;
let guard = self.ddl.lock().unwrap();
let idxs = Self::top_n(guard.as_slice(), question, &query, self.n_results);
Ok(idxs.into_iter().map(|i| guard[i].content.clone()).collect())
}
async fn get_related_documentation(&self, question: &str) -> Result<Vec<String>> {
let query = self.embedding.embed(question).await?;
let guard = self.docs.lock().unwrap();
let idxs = Self::top_n(guard.as_slice(), question, &query, self.n_results);
Ok(idxs.into_iter().map(|i| guard[i].content.clone()).collect())
}
async fn get_similar_question_sql(&self, question: &str) -> Result<Vec<QuestionSql>> {
let query = self.embedding.embed(question).await?;
let guard = self.sql.lock().unwrap();
let idxs = Self::top_n(guard.as_slice(), question, &query, self.n_results);
Ok(idxs
.into_iter()
.map(|i| QuestionSql {
question: guard[i].question.clone().unwrap_or_default(),
sql: guard[i].content.clone(),
})
.collect())
}
async fn all_ddl(&self) -> Result<Vec<String>> {
Ok(self.ddl.lock().unwrap().iter().map(|e| e.content.clone()).collect())
}
async fn all_documentation(&self) -> Result<Vec<String>> {
Ok(self.docs.lock().unwrap().iter().map(|e| e.content.clone()).collect())
}
async fn all_question_sql(&self) -> Result<Vec<QuestionSql>> {
Ok(self
.sql
.lock()
.unwrap()
.iter()
.map(|e| QuestionSql {
question: e.question.clone().unwrap_or_default(),
sql: e.content.clone(),
})
.collect())
}
}