1pub mod memory;
9
10#[cfg(feature = "postgres")]
11pub mod postgres;
12#[cfg(feature = "sqlite")]
13pub mod sqlite;
14
15use crate::config::DbBackend;
16use crate::model::{Chunk, Document, Scored};
17use crate::{RagConfig, Result};
18use async_trait::async_trait;
19use std::sync::Arc;
20
21#[async_trait]
23pub trait VectorStore: Send + Sync {
24 async fn migrate(&self) -> Result<()>;
26
27 async fn upsert_document(&self, doc: &Document) -> Result<()>;
29
30 async fn find_document_by_hash(&self, hash: &str) -> Result<Option<String>>;
33
34 async fn insert_chunks(&self, chunks: &[Chunk]) -> Result<()>;
36
37 async fn vector_search(&self, query: &[f32], k: usize) -> Result<Vec<Scored>>;
40
41 async fn all_chunks(&self) -> Result<Vec<Chunk>>;
44
45 async fn count_chunks(&self) -> Result<usize>;
47
48 async fn count_documents(&self) -> Result<usize>;
50
51 async fn list_documents(&self) -> Result<Vec<Document>>;
53
54 async fn delete_document(&self, doc_id: &str) -> Result<()>;
57
58 async fn delete_documents_by_source(&self, source_uri: &str) -> Result<()>;
61
62 async fn clear(&self) -> Result<()>;
64}
65
66pub async fn from_config(cfg: &RagConfig) -> Result<Arc<dyn VectorStore>> {
68 let store: Arc<dyn VectorStore> = match cfg.db_backend {
69 DbBackend::Memory => Arc::new(memory::MemoryStore::new()),
70 DbBackend::Sqlite => {
71 #[cfg(feature = "sqlite")]
72 {
73 Arc::new(sqlite::SqliteStore::connect(&cfg.database_url, cfg.embed_dim).await?)
74 }
75 #[cfg(not(feature = "sqlite"))]
76 {
77 return Err(crate::RagError::FeatureDisabled(
78 "sqlite".into(),
79 "sqlite".into(),
80 ));
81 }
82 }
83 DbBackend::Postgres => {
84 #[cfg(feature = "postgres")]
85 {
86 Arc::new(postgres::PostgresStore::connect(&cfg.database_url, cfg.embed_dim).await?)
87 }
88 #[cfg(not(feature = "postgres"))]
89 {
90 return Err(crate::RagError::FeatureDisabled(
91 "postgres".into(),
92 "postgres".into(),
93 ));
94 }
95 }
96 };
97 store.migrate().await?;
98 Ok(store)
99}
100
101pub(crate) fn top_k_by_cosine(
104 query: &[f32],
105 candidates: impl IntoIterator<Item = (Chunk, Vec<f32>)>,
106 k: usize,
107) -> Vec<Scored> {
108 let mut scored: Vec<Scored> = candidates
109 .into_iter()
110 .map(|(chunk, emb)| Scored::new(chunk, crate::math::cosine(query, &emb)))
111 .collect();
112 scored.sort_by(|a, b| {
113 b.score
114 .partial_cmp(&a.score)
115 .unwrap_or(std::cmp::Ordering::Equal)
116 });
117 scored.truncate(k);
118 scored
119}