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