1pub mod error;
29pub mod extensions;
30pub mod memory;
31pub mod stub;
32
33#[cfg(feature = "embedding-store")]
34pub mod embedding_store;
35
36#[cfg(feature = "real-pg")]
37pub mod real_pg;
38
39#[cfg(feature = "hybrid-search")]
41pub mod hybrid_search;
42
43pub use error::VectorError;
44pub use extensions::{
45 AnnIndexDef, AnnIndexRegistry, AnnIndexType, BatchOpsExt, DimensionValidator, HnswParams,
46 IvfflatParams, MemoryBatchOps, SimilarityAlgorithms, VectorNormalizer, MAX_VECTOR_DIMENSION,
47 MIN_VECTOR_DIMENSION,
48};
49pub use memory::InMemoryVectorStore;
50pub use stub::StubVectorStore;
51
52#[cfg(feature = "real-pg")]
53pub use real_pg::{RealPgConfig, RealPgVectorStore};
54
55use async_trait::async_trait;
56use std::collections::HashMap;
57use std::str::FromStr;
58
59pub const MAX_TOP_K: usize = 10_000;
66
67pub fn validate_top_k(top_k: usize) -> Result<usize, VectorError> {
73 if top_k == 0 {
74 return Err(VectorError::TopKExceeded {
75 requested: top_k,
76 max: MAX_TOP_K,
77 });
78 }
79 if top_k > MAX_TOP_K {
80 return Err(VectorError::TopKExceeded {
81 requested: top_k,
82 max: MAX_TOP_K,
83 });
84 }
85 Ok(top_k)
86}
87
88#[derive(Debug, Clone)]
90pub struct VectorRecord {
91 pub id: String,
92 pub vector: Vec<f32>,
93 pub score: Option<f32>,
94 pub metadata: Option<HashMap<String, serde_json::Value>>,
95}
96
97impl VectorRecord {
98 pub fn new(id: impl Into<String>, vector: Vec<f32>) -> Self {
99 Self {
100 id: id.into(),
101 vector,
102 score: None,
103 metadata: None,
104 }
105 }
106
107 pub fn with_score(mut self, score: f32) -> Self {
108 self.score = Some(score);
109 self
110 }
111
112 pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
113 self.metadata = Some(metadata);
114 self
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct SearchResult {
121 pub id: String,
122 pub score: f32,
123 pub vector: Vec<f32>,
124 pub text: Option<String>,
125 pub metadata: Option<HashMap<String, serde_json::Value>>,
126}
127
128impl SearchResult {
129 pub fn new(id: impl Into<String>, score: f32, vector: Vec<f32>) -> Self {
130 Self {
131 id: id.into(),
132 score,
133 vector,
134 text: None,
135 metadata: None,
136 }
137 }
138
139 pub fn with_text(mut self, text: impl Into<String>) -> Self {
140 self.text = Some(text.into());
141 self
142 }
143
144 pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
145 self.metadata = Some(metadata);
146 self
147 }
148}
149
150#[derive(Debug, Clone, Copy, Default, PartialEq)]
152pub enum VectorMetric {
153 #[default]
154 Cosine,
155 Euclidean,
156 DotProduct,
157}
158
159impl VectorMetric {
160 pub fn pg_operator(&self) -> &'static str {
162 match self {
163 VectorMetric::Cosine => "<=>",
164 VectorMetric::Euclidean => "<->",
165 VectorMetric::DotProduct => "<#>",
166 }
167 }
168
169 pub fn as_str(&self) -> &'static str {
170 match self {
171 VectorMetric::Cosine => "cosine",
172 VectorMetric::Euclidean => "euclidean",
173 VectorMetric::DotProduct => "dotproduct",
174 }
175 }
176}
177
178impl FromStr for VectorMetric {
179 type Err = String;
180
181 fn from_str(s: &str) -> Result<Self, Self::Err> {
182 match s {
183 "cosine" => Ok(VectorMetric::Cosine),
184 "euclidean" => Ok(VectorMetric::Euclidean),
185 "dotproduct" => Ok(VectorMetric::DotProduct),
186 _ => Err(format!("unknown vector metric: {}", s)),
187 }
188 }
189}
190
191#[async_trait]
196pub trait PgVectorStore: Send + Sync {
197 async fn create_collection(
199 &self,
200 name: &str,
201 dimension: usize,
202 metric: Option<VectorMetric>,
203 ) -> Result<(), VectorError>;
204
205 async fn delete_collection(&self, name: &str) -> Result<(), VectorError>;
207
208 async fn insert(&self, collection: &str, records: Vec<VectorRecord>)
210 -> Result<(), VectorError>;
211
212 async fn search(
217 &self,
218 collection: &str,
219 query: &[f32],
220 top_k: usize,
221 ) -> Result<Vec<SearchResult>, VectorError>;
222
223 async fn get(&self, collection: &str, id: &str) -> Result<Option<VectorRecord>, VectorError>;
225
226 async fn delete(&self, collection: &str, ids: Vec<String>) -> Result<u64, VectorError>;
228
229 async fn count(&self, collection: &str) -> Result<usize, VectorError>;
231}
232#[derive(Debug, Clone)]
237pub struct AnnSearchResult {
238 pub records: Vec<SearchResult>,
240 pub recall_rate: f64,
242 pub latency_ms: u64,
244}
245
246#[cfg(feature = "ann-accel")]
251#[async_trait]
252pub trait AnnAccelerated: PgVectorStore {
253 async fn ann_search(
265 &self,
266 collection: &str,
267 query: &[f32],
268 top_k: usize,
269 tenant_id: Option<&str>,
270 ) -> Result<AnnSearchResult, VectorError>;
271}
272
273#[cfg(feature = "ann-accel")]
274#[async_trait]
275impl AnnAccelerated for InMemoryVectorStore {
276 async fn ann_search(
277 &self,
278 collection: &str,
279 query: &[f32],
280 top_k: usize,
281 tenant_id: Option<&str>,
282 ) -> Result<AnnSearchResult, VectorError> {
283 use std::time::Instant;
284 let start = Instant::now();
285
286 let mut results = self.search(collection, query, top_k).await?;
288
289 if let Some(tid) = tenant_id {
291 results.retain(|r| {
292 r.metadata
293 .as_ref()
294 .and_then(|m| m.get("tenant_id"))
295 .and_then(|v| v.as_str())
296 == Some(tid)
297 });
298 }
299
300 let latency_ms = start.elapsed().as_millis() as u64;
301
302 let recall_rate = 1.0;
304
305 Ok(AnnSearchResult {
306 records: results,
307 recall_rate,
308 latency_ms,
309 })
310 }
311}