1use crate::resident::ResidentIndex;
2use rusqlite::{params, Connection, OptionalExtension};
3use std::path::Path;
4use thiserror::Error;
5
6pub const DEFAULT_VECTOR_DIM: usize = 256;
7const SCHEMA: &str = "CREATE TABLE IF NOT EXISTS rightkit_vectors (
8 key TEXT PRIMARY KEY NOT NULL,
9 model_id TEXT NOT NULL,
10 dimensions INTEGER NOT NULL,
11 vector BLOB NOT NULL
12);";
13
14#[derive(Debug, Error)]
15pub enum VectorError {
16 #[error("SQLite: {0}")]
17 Sqlite(#[from] rusqlite::Error),
18 #[error("vector dimension must be {expected}, got {actual}")]
19 Dimension { expected: usize, actual: usize },
20 #[error("vector contains non-finite values")]
21 NonFinite,
22 #[error("stored vector for {key} is malformed")]
23 Malformed { key: String },
24}
25
26#[derive(Debug, Clone, PartialEq)]
27pub struct Nearest {
28 pub key: String,
29 pub score: f32,
30 pub vector: Vec<f32>,
31}
32
33fn db_error(error: rightkit_db::DbError) -> VectorError {
37 match error {
38 rightkit_db::DbError::Sqlite(e) => VectorError::Sqlite(e),
39 other => VectorError::Sqlite(rusqlite::Error::SqliteFailure(
40 rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_CANTOPEN),
41 Some(other.to_string()),
42 )),
43 }
44}
45
46pub struct VectorStore {
47 conn: Connection,
48 dimension: usize,
49}
50
51impl VectorStore {
52 pub fn open(path: impl AsRef<Path>, dimension: usize) -> Result<Self, VectorError> {
53 let conn = rightkit_db::DbOptions::new(path.as_ref())
54 .open_writer()
55 .map_err(db_error)?;
56 Self::from_connection(conn, dimension)
57 }
58
59 pub fn in_memory(dimension: usize) -> Result<Self, VectorError> {
60 let conn = rightkit_db::open_in_memory().map_err(db_error)?;
61 Self::from_connection(conn, dimension)
62 }
63
64 pub fn from_connection(conn: Connection, dimension: usize) -> Result<Self, VectorError> {
65 if dimension == 0 {
66 return Err(VectorError::Dimension {
67 expected: 1,
68 actual: 0,
69 });
70 }
71 conn.execute_batch(SCHEMA)?;
72 Ok(Self { conn, dimension })
73 }
74
75 pub fn dimension(&self) -> usize {
76 self.dimension
77 }
78
79 pub fn len(&self) -> Result<usize, VectorError> {
80 let count: i64 =
81 self.conn
82 .query_row("SELECT COUNT(*) FROM rightkit_vectors", [], |row| {
83 row.get(0)
84 })?;
85 Ok(count as usize)
86 }
87
88 pub fn is_empty(&self) -> Result<bool, VectorError> {
89 Ok(self.len()? == 0)
90 }
91
92 pub fn put(&self, key: &str, model_id: &str, vector: &[f32]) -> Result<(), VectorError> {
93 validate(vector, self.dimension)?;
94 self.conn.execute(
95 "INSERT INTO rightkit_vectors(key, model_id, dimensions, vector)
96 VALUES (?1, ?2, ?3, ?4)
97 ON CONFLICT(key) DO UPDATE SET model_id=excluded.model_id,
98 dimensions=excluded.dimensions, vector=excluded.vector",
99 params![key, model_id, self.dimension as i64, encode(vector)],
100 )?;
101 Ok(())
102 }
103
104 pub fn put_batch<'a, I>(&mut self, rows: I) -> Result<(), VectorError>
105 where
106 I: IntoIterator<Item = (&'a str, &'a str, &'a [f32])>,
107 {
108 let tx = self.conn.transaction()?;
109 for (key, model_id, vector) in rows {
110 validate(vector, self.dimension)?;
111 tx.execute(
112 "INSERT INTO rightkit_vectors(key, model_id, dimensions, vector)
113 VALUES (?1, ?2, ?3, ?4)
114 ON CONFLICT(key) DO UPDATE SET model_id=excluded.model_id,
115 dimensions=excluded.dimensions, vector=excluded.vector",
116 params![key, model_id, self.dimension as i64, encode(vector)],
117 )?;
118 }
119 tx.commit()?;
120 Ok(())
121 }
122
123 pub fn get(&self, key: &str) -> Result<Option<Nearest>, VectorError> {
124 let row = self
125 .conn
126 .query_row(
127 "SELECT dimensions, vector FROM rightkit_vectors WHERE key=?1",
128 params![key],
129 |row| {
130 let dimensions: i64 = row.get(0)?;
131 let bytes: Vec<u8> = row.get(1)?;
132 Ok((dimensions, bytes))
133 },
134 )
135 .optional()?;
136 row.map(|(dimensions, bytes)| {
137 if dimensions != self.dimension as i64 {
138 return Err(VectorError::Malformed {
139 key: key.to_owned(),
140 });
141 }
142 let vector = decode(&bytes, self.dimension).map_err(|_| VectorError::Malformed {
143 key: key.to_owned(),
144 })?;
145 Ok(Nearest {
146 key: key.to_owned(),
147 score: 1.0,
148 vector,
149 })
150 })
151 .transpose()
152 }
153
154 pub fn nearest(
155 &self,
156 model_id: &str,
157 query: &[f32],
158 limit: usize,
159 ) -> Result<Vec<Nearest>, VectorError> {
160 validate(query, self.dimension)?;
161 if limit == 0 {
162 return Ok(Vec::new());
163 }
164 let mut stmt = self.conn.prepare(
165 "SELECT key, dimensions, vector FROM rightkit_vectors
166 WHERE model_id=?1 ORDER BY key",
167 )?;
168 let rows = stmt.query_map(params![model_id], |row| {
169 let key: String = row.get(0)?;
170 let dimensions: i64 = row.get(1)?;
171 let bytes: Vec<u8> = row.get(2)?;
172 Ok((key, dimensions, bytes))
173 })?;
174 let mut candidates = Vec::new();
175 for row in rows {
176 let (key, dimensions, bytes) = row?;
177 if dimensions != self.dimension as i64 {
178 return Err(VectorError::Malformed { key });
179 }
180 let vector = decode(&bytes, self.dimension)
181 .map_err(|_| VectorError::Malformed { key: key.clone() })?;
182 candidates.push(Nearest {
183 score: cosine_similarity(query, &vector),
184 key,
185 vector,
186 });
187 }
188 candidates.sort_by(|left, right| {
189 right
190 .score
191 .total_cmp(&left.score)
192 .then_with(|| left.key.cmp(&right.key))
193 });
194 candidates.truncate(limit);
195 Ok(candidates)
196 }
197
198 pub fn resident_index(&self, model_id: &str) -> Result<ResidentIndex, VectorError> {
202 let mut stmt = self.conn.prepare(
203 "SELECT key, dimensions, vector FROM rightkit_vectors
204 WHERE model_id=?1 ORDER BY key",
205 )?;
206 let rows = stmt.query_map(params![model_id], |row| {
207 let key: String = row.get(0)?;
208 let dimensions: i64 = row.get(1)?;
209 let bytes: Vec<u8> = row.get(2)?;
210 Ok((key, dimensions, bytes))
211 })?;
212 let mut vectors = Vec::new();
213 for row in rows {
214 let (key, dimensions, bytes) = row?;
215 if dimensions != self.dimension as i64 {
216 return Err(VectorError::Malformed { key });
217 }
218 let vector = decode(&bytes, self.dimension)
219 .map_err(|_| VectorError::Malformed { key: key.clone() })?;
220 vectors.push((key, vector));
221 }
222 ResidentIndex::from_vectors(self.dimension, vectors)
223 }
224
225 pub fn remove(&self, key: &str) -> Result<bool, VectorError> {
226 Ok(self
227 .conn
228 .execute("DELETE FROM rightkit_vectors WHERE key=?1", params![key])?
229 != 0)
230 }
231
232 pub fn clear_model(&self, model_id: &str) -> Result<usize, VectorError> {
233 Ok(self.conn.execute(
234 "DELETE FROM rightkit_vectors WHERE model_id=?1",
235 params![model_id],
236 )?)
237 }
238}
239
240pub fn cosine_similarity(left: &[f32], right: &[f32]) -> f32 {
241 if left.len() != right.len() || left.is_empty() {
242 return 0.0;
243 }
244 let mut dot = 0.0;
245 let mut left_norm = 0.0;
246 let mut right_norm = 0.0;
247 for (&a, &b) in left.iter().zip(right) {
248 dot += a * b;
249 left_norm += a * a;
250 right_norm += b * b;
251 }
252 if left_norm == 0.0 || right_norm == 0.0 {
253 0.0
254 } else {
255 dot / (left_norm.sqrt() * right_norm.sqrt())
256 }
257}
258
259pub(crate) fn validate(vector: &[f32], dimension: usize) -> Result<(), VectorError> {
260 if vector.len() != dimension {
261 return Err(VectorError::Dimension {
262 expected: dimension,
263 actual: vector.len(),
264 });
265 }
266 if vector.iter().any(|value| !value.is_finite()) {
267 return Err(VectorError::NonFinite);
268 }
269 Ok(())
270}
271
272fn encode(vector: &[f32]) -> Vec<u8> {
273 vector
274 .iter()
275 .flat_map(|value| value.to_le_bytes())
276 .collect()
277}
278
279fn decode(bytes: &[u8], dimension: usize) -> Result<Vec<f32>, ()> {
280 if bytes.len() != dimension * std::mem::size_of::<f32>() {
281 return Err(());
282 }
283 let mut vector = Vec::with_capacity(dimension);
284 for chunk in bytes.as_chunks::<4>().0 {
285 vector.push(f32::from_le_bytes(*chunk));
286 }
287 if vector.iter().any(|value| !value.is_finite()) {
288 return Err(());
289 }
290 Ok(vector)
291}